diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b0550f..ef2046b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: - name: Set up a PureScript toolchain uses: purescript-contrib/setup-purescript@main + with: + purs-tidy: "latest" - name: Cache PureScript dependencies uses: actions/cache@v2 @@ -32,3 +34,6 @@ jobs: - name: Run tests run: spago test --no-install + + - name: Check formatting + run: purs-tidy check src test diff --git a/.gitignore b/.gitignore index 7bca306..7e82b68 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ !.gitignore !.github !.editorconfig +!.tidyrc.json output generated-docs diff --git a/.tidyrc.json b/.tidyrc.json new file mode 100644 index 0000000..4f013c1 --- /dev/null +++ b/.tidyrc.json @@ -0,0 +1,10 @@ +{ + "importSort": "source", + "importWrap": "source", + "indent": 2, + "operatorsFile": null, + "ribbon": 1, + "typeArrowPlacement": "first", + "unicode": "never", + "width": null +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c7bc53..32031bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ New features: Bugfixes: Other improvements: +- Added `purs-tidy` formatter (#13 by @thomashoneyman) ## [v2.0.0](https://github.com/purescript-contrib/purescript-concurrent-queues/releases/tag/v2.0.0) - 2021-02-26 diff --git a/src/Concurrent/BoundedQueue.purs b/src/Concurrent/BoundedQueue.purs index 5d3e3b3..e14fb0e 100644 --- a/src/Concurrent/BoundedQueue.purs +++ b/src/Concurrent/BoundedQueue.purs @@ -24,52 +24,52 @@ import Effect.Aff.AVar as AVar import Partial.Unsafe (unsafePartial) -- | Creates a new `BoundedQueue` with the given capacity, -new ∷ ∀ a. Int → Aff (BoundedQueue a) +new :: forall a. Int -> Aff (BoundedQueue a) new size = do - contents ← replicateA size AVar.empty - readPos ← AVar.new 0 - writePos ← AVar.new 0 + contents <- replicateA size AVar.empty + readPos <- AVar.new 0 + writePos <- AVar.new 0 pure (BoundedQueue { size, contents, readPos, writePos }) -- | Writes an element to the given queue. Will block if the queue is full until -- | someone reads from it. -write ∷ ∀ a. BoundedQueue a → a → Aff Unit +write :: forall a. BoundedQueue a -> a -> Aff Unit write (BoundedQueue q) a = do - w ← AVar.take q.writePos + w <- AVar.take q.writePos AVar.put a (unsafePartial unsafeIndex q.contents w) AVar.put ((w + 1) `mod` q.size) q.writePos -- | Reads an element from the given queue, will block if the queue is empty, -- | until someone writes to it. -read ∷ ∀ a. BoundedQueue a → Aff a +read :: forall a. BoundedQueue a -> Aff a read (BoundedQueue q) = do - r ← AVar.take q.readPos - v ← AVar.take (unsafePartial unsafeIndex q.contents r) + r <- AVar.take q.readPos + v <- AVar.take (unsafePartial unsafeIndex q.contents r) AVar.put ((r + 1) `mod` q.size) q.readPos pure v -- | Checks whether the given queue is empty. Never blocks. -isEmpty ∷ ∀ a. BoundedQueue a → Aff Boolean +isEmpty :: forall a. BoundedQueue a -> Aff Boolean isEmpty (BoundedQueue q) = do AVar.tryRead q.readPos >>= case _ of - Nothing → pure true - Just r → AVar.tryRead (unsafePartial unsafeIndex q.contents r) <#> case _ of - Nothing → true - Just _ → false + Nothing -> pure true + Just r -> AVar.tryRead (unsafePartial unsafeIndex q.contents r) <#> case _ of + Nothing -> true + Just _ -> false -- | Attempts to read an element from the given queue. If the queue is empty, -- | returns `Nothing`. -- | -- | *Careful!* If other readers are blocked on the queue `tryRead` will also -- | block. -tryRead ∷ ∀ a. BoundedQueue a → Aff (Maybe a) +tryRead :: forall a. BoundedQueue a -> Aff (Maybe a) tryRead (BoundedQueue q) = do - r ← AVar.take q.readPos + r <- AVar.take q.readPos AVar.tryTake (unsafePartial unsafeIndex q.contents r) >>= case _ of - Just v → do + Just v -> do AVar.put ((r + 1) `mod` q.size) q.readPos pure (Just v) - Nothing → do + Nothing -> do AVar.put r q.readPos pure Nothing @@ -78,11 +78,11 @@ tryRead (BoundedQueue q) = do -- | -- | *Careful!* If other writers are blocked on the queue `tryWrite` will also -- | block. -tryWrite ∷ ∀ a. BoundedQueue a → a → Aff Boolean +tryWrite :: forall a. BoundedQueue a -> a -> Aff Boolean tryWrite (BoundedQueue q) a = do - w ← AVar.take q.writePos - AVar.tryPut a (unsafePartial unsafeIndex q.contents w) >>= if _ - then do + w <- AVar.take q.writePos + AVar.tryPut a (unsafePartial unsafeIndex q.contents w) >>= + if _ then do AVar.put ((w + 1) `mod` q.size) q.writePos pure true else do diff --git a/src/Concurrent/BoundedQueue/Internal.purs b/src/Concurrent/BoundedQueue/Internal.purs index 336b7e9..fed4f4b 100644 --- a/src/Concurrent/BoundedQueue/Internal.purs +++ b/src/Concurrent/BoundedQueue/Internal.purs @@ -1,13 +1,13 @@ -module Concurrent.BoundedQueue.Internal - ( BoundedQueue(..) - ) where - -import Effect.AVar (AVar) - -newtype BoundedQueue a = - BoundedQueue - { size ∷ Int - , contents ∷ Array (AVar a) - , readPos ∷ AVar Int - , writePos ∷ AVar Int - } +module Concurrent.BoundedQueue.Internal + ( BoundedQueue(..) + ) where + +import Effect.AVar (AVar) + +newtype BoundedQueue a = + BoundedQueue + { size :: Int + , contents :: Array (AVar a) + , readPos :: AVar Int + , writePos :: AVar Int + } diff --git a/src/Concurrent/BoundedQueue/Sync.purs b/src/Concurrent/BoundedQueue/Sync.purs index 86efac8..a96e3ae 100644 --- a/src/Concurrent/BoundedQueue/Sync.purs +++ b/src/Concurrent/BoundedQueue/Sync.purs @@ -1,69 +1,68 @@ -module Concurrent.BoundedQueue.Sync - ( new - , isEmpty - , tryRead - , tryWrite - , module Export - ) -where - -import Prelude - -import Concurrent.BoundedQueue.Internal (BoundedQueue(..)) -import Concurrent.BoundedQueue.Internal (BoundedQueue) as Export -import Data.Array (unsafeIndex) -import Data.Maybe (Maybe(..)) -import Data.Unfoldable (replicateA) -import Effect (Effect) -import Effect.AVar as AVarEff -import Partial.Unsafe (unsafePartial) - --- | Synchronously creates a new `BoundedQueue` with the given capacity. -new ∷ ∀ a. Int → Effect (BoundedQueue a) -new size = do - contents ← replicateA size AVarEff.empty - readPos ← AVarEff.new 0 - writePos ← AVarEff.new 0 - pure (BoundedQueue { size, contents, readPos, writePos }) - --- | Synchronously checks whether the given queue is empty. Never blocks. -isEmpty ∷ ∀ a. BoundedQueue a → Effect Boolean -isEmpty (BoundedQueue q) = do - AVarEff.tryRead q.readPos >>= case _ of - Nothing → pure true - Just r → AVarEff.tryRead (unsafePartial unsafeIndex q.contents r) <#> - case _ of - Nothing → true - Just _ → false - --- | Synchronously attempts to read an element from the given queue. If the --- | queue is empty, or there is a concurrent reader, returns `Nothing`. -tryRead ∷ ∀ a. BoundedQueue a -> Effect (Maybe a) -tryRead (BoundedQueue q) = do - mr ← AVarEff.tryTake q.readPos - case mr of - Just r → do - AVarEff.tryTake (unsafePartial unsafeIndex q.contents r) >>= case _ of - Just v → do - _ <- AVarEff.tryPut ((r + 1) `mod` q.size) q.readPos - pure (Just v) - Nothing → do - _ <- AVarEff.tryPut r q.readPos - pure Nothing - Nothing → pure Nothing - --- | Attempts to write an element into the given queue. If the queue is full, --- | or there is a concurrent writer, returns `false` otherwise `true`. -tryWrite ∷ ∀ a. BoundedQueue a → a → Effect Boolean -tryWrite (BoundedQueue q) a = do - mw ← AVarEff.tryTake q.writePos - case mw of - Just w → do - AVarEff.tryPut a (unsafePartial unsafeIndex q.contents w) >>= if _ - then do - _ ← AVarEff.tryPut ((w + 1) `mod` q.size) q.writePos - pure true - else do - _ ← AVarEff.tryPut w q.writePos - pure false - Nothing → pure false +module Concurrent.BoundedQueue.Sync + ( new + , isEmpty + , tryRead + , tryWrite + , module Export + ) where + +import Prelude + +import Concurrent.BoundedQueue.Internal (BoundedQueue(..)) +import Concurrent.BoundedQueue.Internal (BoundedQueue) as Export +import Data.Array (unsafeIndex) +import Data.Maybe (Maybe(..)) +import Data.Unfoldable (replicateA) +import Effect (Effect) +import Effect.AVar as AVarEff +import Partial.Unsafe (unsafePartial) + +-- | Synchronously creates a new `BoundedQueue` with the given capacity. +new :: forall a. Int -> Effect (BoundedQueue a) +new size = do + contents <- replicateA size AVarEff.empty + readPos <- AVarEff.new 0 + writePos <- AVarEff.new 0 + pure (BoundedQueue { size, contents, readPos, writePos }) + +-- | Synchronously checks whether the given queue is empty. Never blocks. +isEmpty :: forall a. BoundedQueue a -> Effect Boolean +isEmpty (BoundedQueue q) = do + AVarEff.tryRead q.readPos >>= case _ of + Nothing -> pure true + Just r -> AVarEff.tryRead (unsafePartial unsafeIndex q.contents r) <#> + case _ of + Nothing -> true + Just _ -> false + +-- | Synchronously attempts to read an element from the given queue. If the +-- | queue is empty, or there is a concurrent reader, returns `Nothing`. +tryRead :: forall a. BoundedQueue a -> Effect (Maybe a) +tryRead (BoundedQueue q) = do + mr <- AVarEff.tryTake q.readPos + case mr of + Just r -> do + AVarEff.tryTake (unsafePartial unsafeIndex q.contents r) >>= case _ of + Just v -> do + _ <- AVarEff.tryPut ((r + 1) `mod` q.size) q.readPos + pure (Just v) + Nothing -> do + _ <- AVarEff.tryPut r q.readPos + pure Nothing + Nothing -> pure Nothing + +-- | Attempts to write an element into the given queue. If the queue is full, +-- | or there is a concurrent writer, returns `false` otherwise `true`. +tryWrite :: forall a. BoundedQueue a -> a -> Effect Boolean +tryWrite (BoundedQueue q) a = do + mw <- AVarEff.tryTake q.writePos + case mw of + Just w -> do + AVarEff.tryPut a (unsafePartial unsafeIndex q.contents w) >>= + if _ then do + _ <- AVarEff.tryPut ((w + 1) `mod` q.size) q.writePos + pure true + else do + _ <- AVarEff.tryPut w q.writePos + pure false + Nothing -> pure false diff --git a/src/Concurrent/Queue.purs b/src/Concurrent/Queue.purs index 2640da3..d4c12aa 100644 --- a/src/Concurrent/Queue.purs +++ b/src/Concurrent/Queue.purs @@ -16,35 +16,35 @@ import Effect.Aff.AVar as AVar -- | An unbounded Queue fit for concurrent access. newtype Queue a = Queue - { readEnd ∷ AVar (Stream a) - , writeEnd ∷ AVar (Stream a) + { readEnd :: AVar (Stream a) + , writeEnd :: AVar (Stream a) } type Stream a = AVar (QItem a) data QItem a = QItem a (Stream a) -- | Creates a new `Queue`. -new ∷ ∀ a. Aff (Queue a) +new :: forall a. Aff (Queue a) new = do - hole ← AVar.empty - readEnd ← AVar.new hole - writeEnd ← AVar.new hole + hole <- AVar.empty + readEnd <- AVar.new hole + writeEnd <- AVar.new hole pure (Queue { readEnd, writeEnd }) -- | Writes a new value into the queue -write ∷ ∀ a. Queue a → a → Aff Unit +write :: forall a. Queue a -> a -> Aff Unit write (Queue q) a = do - newHole ← AVar.empty - oldHole ← AVar.take q.writeEnd + newHole <- AVar.empty + oldHole <- AVar.take q.writeEnd AVar.put (QItem a newHole) oldHole AVar.put newHole q.writeEnd -- | Reads a value from the queue. Blocks if the queue is empty, and resumes -- | when it has been written to. -read ∷ ∀ a. Queue a → Aff a +read :: forall a. Queue a -> Aff a read (Queue q) = do - readEnd ← AVar.take q.readEnd - QItem a newRead ← AVar.read readEnd + readEnd <- AVar.take q.readEnd + QItem a newRead <- AVar.read readEnd AVar.put newRead q.readEnd pure a @@ -53,13 +53,13 @@ read (Queue q) = do -- | -- | *CAREFUL!* This will block if other readers are blocked on the -- | queue. -tryRead ∷ ∀ a. Queue a → Aff (Maybe a) +tryRead :: forall a. Queue a -> Aff (Maybe a) tryRead (Queue q) = do - readEnd ← AVar.take q.readEnd + readEnd <- AVar.take q.readEnd AVar.tryRead readEnd >>= case _ of - Just (QItem a newRead) → do + Just (QItem a newRead) -> do AVar.put newRead q.readEnd pure (Just a) - Nothing → do + Nothing -> do AVar.put readEnd q.readEnd pure Nothing diff --git a/test/BoundedQueue.purs b/test/BoundedQueue.purs index 0e93395..f4c1147 100644 --- a/test/BoundedQueue.purs +++ b/test/BoundedQueue.purs @@ -15,156 +15,154 @@ import Effect.Class (liftEffect) import Effect.Aff.Class (class MonadAff, liftAff) import Test.Util (suite, test, shouldEqual, assert, assertFalse) -race ∷ ∀ a b. Aff a → Aff b → Aff (Either a b) +race :: forall a b. Aff a -> Aff b -> Aff (Either a b) race a b = sequential ((parallel (map Left a)) <|> (parallel (map Right b))) -delayMs ∷ Int → Aff Unit +delayMs :: Int -> Aff Unit delayMs = delay <<< Milliseconds <<< toNumber -boundedQueueSuite ∷ forall m. MonadReader Int m ⇒ MonadAff m ⇒ m Unit +boundedQueueSuite :: forall m. MonadReader Int m => MonadAff m => m Unit boundedQueueSuite = do suite "Simple operations" do test "inserting and popping elements" $ liftAff do - q ← BQ.new 2 + q <- BQ.new 2 BQ.write q 1 BQ.write q 2 - r1 ← BQ.read q - r2 ← BQ.read q + r1 <- BQ.read q + r2 <- BQ.read q r1 `shouldEqual` 1 r2 `shouldEqual` 2 suite "Blocking and unblocking" do test "writing more than the allowed capacity blocks" $ liftAff do - q ← BQ.new 1 + q <- BQ.new 1 BQ.write q 1 - r ← race (delayMs 50) (BQ.write q 2) + r <- race (delayMs 50) (BQ.write q 2) assert "Not blocked" (isLeft r) test "reading unblocks writes blocked on missing capacity" $ liftAff do - q ← BQ.new 1 + q <- BQ.new 1 BQ.write q 1 - _ ← forkAff (delayMs 20 *> (BQ.read q)) - r ← race (delayMs 50) (BQ.write q 2) + _ <- forkAff (delayMs 20 *> (BQ.read q)) + r <- race (delayMs 50) (BQ.write q 2) assert "Blocked too long" (isRight r) suite "isEmpty" do test "an empty queue is empty" $ liftAff do - q ← BQ.new 1 - r ← BQ.isEmpty q + q <- BQ.new 1 + r <- BQ.isEmpty q assert "" r test "an empty queue with blocked readers is empty" $ liftAff do - q ← BQ.new 1 - _ ← forkAff (BQ.read q) - r ← BQ.isEmpty q + q <- BQ.new 1 + _ <- forkAff (BQ.read q) + r <- BQ.isEmpty q assert "" r suite "tryRead blocking and unblocking" do test "tryRead is non-blocking for empty queue" $ liftAff do - q ← BQ.new 1 - r ← BQ.tryRead q + q <- BQ.new 1 + r <- BQ.tryRead q assert "Should've been Nothing" (isNothing r) test "tryRead reads from a non-empty queue" $ liftAff do - q ← BQ.new 1 + q <- BQ.new 1 BQ.write q 1 - r1 ← BQ.tryRead q - r2 ← BQ.tryRead q + r1 <- BQ.tryRead q + r2 <- BQ.tryRead q r1 `shouldEqual` (Just 1) assert "Should've been Nothing" (isNothing r2) test "tryRead blocks when there are consumers blocked on the queue" $ liftAff do - q ← BQ.new 1 - _ ← forkAff (BQ.read q) - r ← race (delayMs 20) (BQ.tryRead q) + q <- BQ.new 1 + _ <- forkAff (BQ.read q) + r <- race (delayMs 20) (BQ.tryRead q) assert "Should've been Left" (isLeft r) suite "tryWrite blocking and unblocking" do test "tryWrite is non-blocking for full queue" $ liftAff do - q ← BQ.new 1 + q <- BQ.new 1 BQ.write q 1 - r ← BQ.tryWrite q 2 + r <- BQ.tryWrite q 2 assertFalse "Write should've failed" r test "tryWrite writes to a non-full queue" $ liftAff do - q ← BQ.new 1 - rw ← BQ.tryWrite q 1 - r ← BQ.read q + q <- BQ.new 1 + rw <- BQ.tryWrite q 1 + r <- BQ.read q assert "tryWrite should've succeeded" rw r `shouldEqual` 1 test "tryWrite blocks when there are writers blocked on the queue" $ liftAff do - q ← BQ.new 1 + q <- BQ.new 1 BQ.write q 1 - _ ← forkAff (BQ.write q 2) - r ← race (delayMs 20) (BQ.tryWrite q 2) + _ <- forkAff (BQ.write q 2) + r <- race (delayMs 20) (BQ.tryWrite q 2) assert "Should've been Left" (isLeft r) -boundedQueueSyncSuite :: forall m. MonadReader Int m ⇒ MonadAff m ⇒ m Unit +boundedQueueSyncSuite :: forall m. MonadReader Int m => MonadAff m => m Unit boundedQueueSyncSuite = do suite "(Sync) Simple operations" do test "(Sync) inserting and popping elements" $ liftAff do - Tuple r1 r2 ← liftEffect do - q ← liftEffect (BQS.new 2) - _ ← BQS.tryWrite q 1 - _ ← BQS.tryWrite q 2 - r1 ← BQS.tryRead q - r2 ← BQS.tryRead q + Tuple r1 r2 <- liftEffect do + q <- liftEffect (BQS.new 2) + _ <- BQS.tryWrite q 1 + _ <- BQS.tryWrite q 2 + r1 <- BQS.tryRead q + r2 <- BQS.tryRead q pure (Tuple r1 r2) r1 `shouldEqual` (Just 1) r2 `shouldEqual` (Just 2) suite "(Sync) Blocking and unblocking" do test "(Sync) writing more than the allowed capacity blocks" $ liftAff do - q ← liftEffect (BQS.new 1) + q <- liftEffect (BQS.new 1) BQ.write q 1 - r ← race (delayMs 50) (BQ.write q 2) + r <- race (delayMs 50) (BQ.write q 2) assert "Not blocked" (isLeft r) test "(Sync) reading unblocks writes blocked on missing capacity" $ liftAff do - q ← liftEffect (BQS.new 1) + q <- liftEffect (BQS.new 1) BQ.write q 1 - _ ← forkAff (delayMs 20 *> (BQ.read q)) - r ← race (delayMs 50) (BQ.write q 2) + _ <- forkAff (delayMs 20 *> (BQ.read q)) + r <- race (delayMs 50) (BQ.write q 2) assert "Blocked too long" (isRight r) suite "(Sync) isEmpty" do test "(Sync) an empty queue is empty" $ liftAff do - r ← liftEffect do - q ← BQS.new 1 + r <- liftEffect do + q <- BQS.new 1 BQS.isEmpty q assert "" r test "(Sync) an empty queue with blocked readers is empty" $ liftAff do - q ← liftEffect (BQS.new 1) - _ ← forkAff (BQ.read q) - r ← liftEffect (BQS.isEmpty q) + q <- liftEffect (BQS.new 1) + _ <- forkAff (BQ.read q) + r <- liftEffect (BQS.isEmpty q) assert "" r suite "(Sync) tryRead blocking and unblocking" do test "(Sync) tryRead is non-blocking for empty queue" $ liftAff do - r ← liftEffect do - q ← BQS.new 1 + r <- liftEffect do + q <- BQS.new 1 BQS.tryRead q assert "Should've been Nothing" (isNothing r) test "(Sync) tryRead reads from a non-empty queue" $ liftAff do - q ← liftEffect (BQS.new 1) + q <- liftEffect (BQS.new 1) BQ.write q 1 - Tuple r1 r2 ← liftEffect do - r1 ← BQS.tryRead q - r2 ← BQS.tryRead q + Tuple r1 r2 <- liftEffect do + r1 <- BQS.tryRead q + r2 <- BQS.tryRead q pure (Tuple r1 r2) r1 `shouldEqual` (Just 1) assert "Should've been Nothing" (isNothing r2) - test ("(Sync) tryRead does not block when there are consumers blocked on " - <> "the queue") $ liftAff do - q ← liftEffect (BQS.new 1) - _ ← forkAff (BQ.read q) - r ← race (delayMs 20) (liftEffect (BQS.tryRead q)) + test "(Sync) tryRead does not block when there are consumers blocked on the queue" $ liftAff do + q <- liftEffect (BQS.new 1) + _ <- forkAff (BQ.read q) + r <- race (delayMs 20) (liftEffect (BQS.tryRead q)) assert "Should've been Right" (isRight r) suite "(Sync) tryWrite blocking and unblocking" do test "(Sync) tryWrite is non-blocking for full queue" $ liftAff do - q ← liftEffect (BQS.new 1) + q <- liftEffect (BQS.new 1) BQ.write q 1 - r ← liftEffect (BQS.tryWrite q 2) + r <- liftEffect (BQS.tryWrite q 2) assertFalse "Write should've failed" r test "(Sync) tryWrite writes to a non-full queue" $ liftAff do - Tuple q rw ← liftEffect do - q ← BQS.new 1 - rw ← BQS.tryWrite q 1 + Tuple q rw <- liftEffect do + q <- BQS.new 1 + rw <- BQS.tryWrite q 1 pure (Tuple q rw) - r ← BQ.read q + r <- BQ.read q assert "tryWrite should've succeeded" rw r `shouldEqual` 1 - test ("(Sync) tryWrite does not block when there are writers blocked on " <> - "the queue") $ liftAff do - q ← liftEffect (BQS.new 1) + test "(Sync) tryWrite does not block when there are writers blocked on the queue" $ liftAff do + q <- liftEffect (BQS.new 1) BQ.write q 1 - _ ← forkAff (BQ.write q 2) - r ← race (delayMs 20) (liftEffect (BQS.tryWrite q 2)) + _ <- forkAff (BQ.write q 2) + r <- race (delayMs 20) (liftEffect (BQS.tryWrite q 2)) assert "Should've been Right" (isRight r) diff --git a/test/Main.purs b/test/Main.purs index 49f2dac..03573e2 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -9,7 +9,7 @@ import Test.BoundedQueue (boundedQueueSuite, boundedQueueSyncSuite) import Test.Queue (queueSuite) import Test.Util (suite) -main ∷ Effect Unit +main :: Effect Unit main = launchAff_ $ flip runReaderT 0 do suite "Queue" queueSuite suite "BoundedQueue" boundedQueueSuite diff --git a/test/Queue.purs b/test/Queue.purs index 69c7707..8c0672a 100644 --- a/test/Queue.purs +++ b/test/Queue.purs @@ -12,50 +12,50 @@ import Effect.Aff (Aff, Milliseconds(..), delay, forkAff, parallel, sequential) import Effect.Aff.Class (class MonadAff, liftAff) import Test.Util (suite, test, shouldEqual, assert) -race ∷ ∀ a b. Aff a → Aff b → Aff (Either a b) +race :: forall a b. Aff a -> Aff b -> Aff (Either a b) race a b = sequential ((parallel (map Left a)) <|> (parallel (map Right b))) -delayMs ∷ Int → Aff Unit +delayMs :: Int -> Aff Unit delayMs = delay <<< Milliseconds <<< toNumber -queueSuite ∷ forall m. MonadReader Int m ⇒ MonadAff m ⇒ m Unit +queueSuite :: forall m. MonadReader Int m => MonadAff m => m Unit queueSuite = do suite "Simple operations" do test "inserting and popping elements" $ liftAff do - q ← Q.new + q <- Q.new Q.write q 1 Q.write q 2 - r1 ← Q.read q - r2 ← Q.read q + r1 <- Q.read q + r2 <- Q.read q Q.write q 3 - r3 ← Q.read q + r3 <- Q.read q r1 `shouldEqual` 1 r2 `shouldEqual` 2 r3 `shouldEqual` 3 suite "Blocking and unblocking" do test "reading from an empty Queue blocks" $ liftAff do - q ← Q.new - r ← race (delayMs 50) (Q.read q) + q <- Q.new + r <- race (delayMs 50) (Q.read q) assert "Not blocked" (isLeft r) test "writing unblocks reads" $ liftAff do - q ← Q.new - _ ← forkAff (delayMs 20 *> (Q.write q 1)) - r ← race (delayMs 50) (Q.read q) + q <- Q.new + _ <- forkAff (delayMs 20 *> (Q.write q 1)) + r <- race (delayMs 50) (Q.read q) assert "Blocked too long" (isRight r) suite "tryRead blocking and unblocking" do test "tryRead is non-blocking for empty queue" $ liftAff do - q ← Q.new - r ← Q.tryRead q + q <- Q.new + r <- Q.tryRead q assert "Should've been Nothing" (isNothing r) test "tryRead reads from a non-empty queue" $ liftAff do - q ← Q.new + q <- Q.new Q.write q 1 - r1 ← Q.tryRead q - r2 ← Q.tryRead q + r1 <- Q.tryRead q + r2 <- Q.tryRead q r1 `shouldEqual` (Just 1) assert "Should've been Nothing" (isNothing r2) test "tryRead blocks when there are consumers blocked on the queue" $ liftAff do - q ← Q.new - _ ← forkAff (Q.read q) - r ← race (delayMs 20) (Q.tryRead q) + q <- Q.new + _ <- forkAff (Q.read q) + r <- race (delayMs 20) (Q.tryRead q) assert "Should've been Left" (isLeft r) diff --git a/test/Util.purs b/test/Util.purs index 3fb1b99..1d618b0 100644 --- a/test/Util.purs +++ b/test/Util.purs @@ -14,17 +14,17 @@ import Test.Assert (assertEqual) -- Provide similar API to purescript-test-unit to reduce code changes -suite :: forall m. MonadReader Int m ⇒ MonadAff m ⇒ String -> m Unit -> m Unit +suite :: forall m. MonadReader Int m => MonadAff m => String -> m Unit -> m Unit suite = test -test :: forall m. MonadReader Int m ⇒ MonadAff m ⇒ String -> m Unit -> m Unit +test :: forall m. MonadReader Int m => MonadAff m => String -> m Unit -> m Unit test msg runTest = do indentation <- ask let spacing = guard (indentation > 0) " " liftEffect $ log $ (power ">>" indentation) <> spacing <> msg local (_ + 1) runTest -shouldEqual :: forall m a. MonadAff m ⇒ Eq a ⇒ Show a ⇒ a -> a -> m Unit +shouldEqual :: forall m a. MonadAff m => Eq a => Show a => a -> a -> m Unit shouldEqual actual expected = liftEffect $ assertEqual { actual, expected }