packages feed

chiasma-test 0.11.0.0 → 0.12.0.0

raw patch · 4 files changed

+171/−15 lines, 4 filesdep ~basedep ~lensdep ~polysemy-testPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, lens, polysemy-test, prelate, typed-process

API changes (from Hackage documentation)

- Chiasma.Test.Tmux: withTestTmux :: forall t d (r :: EffectRow) a. Members '[Test, Time t d, Log, Resource, Stop TmuxError, Error Text :: (Type -> Type) -> Type -> Type, Race, Async, Embed IO] r => TmuxTestConfig -> Sem (TestTmuxEffects ++ r) a -> Path Abs Dir -> Sem r a
+ Chiasma.Test.Tmux: withTestTmux :: forall t d (r :: EffectRow) a. Members '[Test, Time t d, Log, Resource, Stop TmuxError, Error Text :: (Type -> Type) -> Type -> Type, Race, Async, Final IO, Embed IO] r => TmuxTestConfig -> Sem (TestTmuxEffects ++ r) a -> Path Abs Dir -> Sem r a

Files

chiasma-test.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           chiasma-test-version:        0.11.0.0+version:        0.12.0.0 synopsis:       Testing tools for chiasma description:    See https://hackage.haskell.org/package/chiasma-test/docs/Chiasma-Test.html category:       Terminal@@ -71,7 +71,7 @@       ViewPatterns   ghc-options: -fplugin=Polysemy.Plugin -Wall -Widentities -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wredundant-constraints -Wunused-type-patterns -Wunused-packages   build-depends:-      base <5+      base ==4.*     , bytestring     , chiasma     , chronos@@ -85,11 +85,11 @@     , polysemy-log     , polysemy-plugin     , polysemy-process-    , polysemy-test+    , polysemy-test >=0.6     , polysemy-time-    , prelate+    , prelate ==0.9.*     , text-    , typed-process+    , typed-process >=0.2   mixins:       base hiding (Prelude)     , prelate (Prelate as Prelude)@@ -146,15 +146,15 @@       ViewPatterns   ghc-options: -fplugin=Polysemy.Plugin -threaded -rtsopts -with-rtsopts=-N -Wall -Widentities -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wredundant-constraints -Wunused-type-patterns -Wunused-packages   build-depends:-      base <5+      base ==4.*     , chiasma     , chiasma-test     , hedgehog-    , lens+    , lens >=4     , polysemy     , polysemy-plugin-    , polysemy-test-    , prelate+    , polysemy-test >=0.6+    , prelate ==0.9.*     , tasty     , tasty-hedgehog   mixins:@@ -168,6 +168,7 @@   main-is: Main.hs   other-modules:       Chiasma.Test.CaptureTest+      Chiasma.Test.ConcurrentTest       Chiasma.Test.FindTest       Chiasma.Test.RenderTest       Chiasma.Test.TmuxTest@@ -212,17 +213,16 @@       ViewPatterns   ghc-options: -fplugin=Polysemy.Plugin -threaded -rtsopts -with-rtsopts=-N -Wall -Widentities -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wredundant-constraints -Wunused-type-patterns -Wunused-packages   build-depends:-      base <5+      base ==4.*     , chiasma     , chiasma-test     , hedgehog-    , lens     , path-io     , polysemy     , polysemy-chronos     , polysemy-plugin-    , polysemy-test-    , prelate+    , polysemy-test >=0.6+    , prelate ==0.9.*     , tasty     , tasty-hedgehog   mixins:
+ integration/Chiasma/Test/ConcurrentTest.hs view
@@ -0,0 +1,153 @@+module Chiasma.Test.ConcurrentTest where++import qualified Conc+import Conc (withAsyncGated_, withAsync_)+import qualified Data.Text as Text+import Data.Text (stripPrefix)+import Exon (exon)+import Gate (Gate, signal)+import Polysemy.Test (Hedgehog, UnitTest, assert, (===))+import qualified Queue+import Queue (QueueResult (..))+import qualified Time+import Time (MilliSeconds (MilliSeconds))++import Chiasma.Codec.Data.Window (Window (..))+import Chiasma.Data.CodecError (CodecError)+import Chiasma.Data.TmuxCommand (TmuxCommand (ListWindows, NewWindow))+import Chiasma.Data.TmuxError (TmuxError)+import Chiasma.Data.TmuxNotification (TmuxNotification (..))+import qualified Chiasma.Effect.TmuxApi as TmuxApi+import Chiasma.Effect.TmuxApi (Tmux)+import Chiasma.Effect.TmuxClient (NativeTmux)+import Chiasma.Test.Tmux (tmuxTest)+import Chiasma.Tmux (withTmux)++-- | Run a loop of create-window then list-windows commands, collecting results.+worker ::+  Members [Tmux, AtomicState [Text]] r =>+  Text ->+  Sem r ()+worker label =+  for_ @[] [1 .. 10 :: Int] \ i -> do+    win <- TmuxApi.send (NewWindow def)+    wins <- TmuxApi.send (ListWindows def)+    atomicModify' (<> [[exon|#{label}-#{show i}: created @#{show win.windowId}, total #{show (length wins)}|]])++-- | Stress test for concurrent TmuxClient access.+-- Two threads run inside a single @withTmux@ scope, each sending 20 commands+-- (10 iterations × create + list). The Lock in tmuxRequest serializes+-- send+receive pairs, ensuring correct response routing.+test_concurrent :: UnitTest+test_concurrent =+  tmuxTest do+    restop @TmuxError @NativeTmux $ withTmux @TmuxCommand @CodecError do+      restop @CodecError @Tmux do+        Conc.interpretAtomic @[Text] [] do+          withAsync_ (worker "A") (worker "B")+          wins <- TmuxApi.send (ListWindows def)+          log' <- atomicGet+          for_ log' (embed . putStrLn . toString)+          -- 10 windows from each thread + 1 initial = 21+          length wins === 21++-- | Test that tmux control mode notifications are captured and accessible+-- via the ReceiveNotification action.+--+-- A background thread consumes notifications and writes them to a Queue.+-- Three phases exercise different consumption patterns:+-- 1. Send 3 requests, take from queue after each+-- 2. Send 3 requests, take 3 after the last one+-- 3. Run 3 concurrent threads that each send 3 requests, take 9 after all finish+--+-- All notifications are asserted to be valid (non-empty, starting with '%').+test_notifications :: UnitTest+test_notifications =+  tmuxTest do+    restop @TmuxError @NativeTmux $ withTmux @TmuxCommand @CodecError do+      restop @CodecError @Tmux do+        Conc.interpretQueueTB @TmuxNotification 64 do+          Conc.interpretGates do+            withAsyncGated_ consumeNotifications do+              -- Phase 1: interleaved send/take+              phase1 <- for @[] [1 .. 3 :: Int] \ _ -> do+                void $ TmuxApi.send (NewWindow def)+                Time.sleep (MilliSeconds 10)+                takeNotifications 1+              let notes1 = concat phase1+              assertOrdered notes1++              -- Phase 2: batch send, then batch take+              for_ @[] [1 .. 3 :: Int] \ _ -> do+                void $ TmuxApi.send (NewWindow def)+                Time.sleep (MilliSeconds 10)+              notes2 <- takeNotifications 3+              assertOrdered notes2++              -- Phase 3: concurrent senders, batch take+              withAsync_ sender $ withAsync_ sender sender+              notes3 <- takeNotifications 9+              assertOrdered notes3++              -- Global ordering: each phase's notifications should follow the previous+              assertOrdered (notes1 <> notes2 <> notes3)+  where+    consumeNotifications ::+      Members [Tmux, Queue TmuxNotification, Gate] r =>+      Sem r ()+    consumeNotifications = do+      signal+      forever do+        n <- TmuxApi.receiveNotification+        Queue.write n++    sender ::+      Members [Tmux, Time t d] r =>+      Sem r ()+    sender =+      for_ @[] [1 .. 3 :: Int] \ _ -> do+        void $ TmuxApi.send (NewWindow def)+        Time.sleep (MilliSeconds 10)++    takeNotifications ::+      Member (Queue TmuxNotification) r =>+      Int ->+      Sem r [TmuxNotification]+    takeNotifications n =+      catMaybes <$> replicateM n takeOne++    takeOne ::+      Member (Queue TmuxNotification) r =>+      Sem r (Maybe TmuxNotification)+    takeOne =+      Queue.readTimeout (MilliSeconds 500) <&> \case+        Success a -> Just a+        _ -> Nothing++    assertOrdered ::+      Member (Hedgehog IO) r =>+      [TmuxNotification] ->+      Sem r ()+    assertOrdered notes = do+      assert (not (null notes))+      for_ notes \ n ->+        assert (not (Text.null n.name))+      assertNonDecreasing (mapMaybe notificationId notes)++    assertNonDecreasing ::+      Member (Hedgehog IO) r =>+      [Int] ->+      Sem r ()+    assertNonDecreasing = \case+      [] -> unit+      [_] -> unit+      a : b : rest -> do+        assert (a <= b)+        assertNonDecreasing (b : rest)++    notificationId :: TmuxNotification -> Maybe Int+    notificationId n =+      listToMaybe n.args >>= extractId++    extractId :: Text -> Maybe Int+    extractId = readMaybe . toString <=< stripPrefix "@"
integration/Main.hs view
@@ -1,6 +1,7 @@ module Main where  import Chiasma.Test.CaptureTest (test_capture)+import Chiasma.Test.ConcurrentTest (test_concurrent, test_notifications) import Chiasma.Test.FindTest (test_find) import Chiasma.Test.RenderTest (test_render) import Chiasma.Test.TmuxTest (test_tmux)@@ -13,7 +14,9 @@     integrationTest "basic tmux commands" test_tmux,     integrationTest "find pane by ID" test_find,     test_render,-    integrationTest "capture pane content" test_capture+    integrationTest "capture pane content" test_capture,+    integrationTest "concurrent tmux access" test_concurrent,+    integrationTest "tmux notifications" test_notifications   ]  main :: IO ()
lib/Chiasma/Test/Tmux.hs view
@@ -145,7 +145,7 @@   ]  withTestTmux ::-  Members [Test, Time t d, Log, Resource, Stop TmuxError, Error Text, Race, Async, Embed IO] r =>+  Members [Test, Time t d, Log, Resource, Stop TmuxError, Error Text, Race, Async, Final IO, Embed IO] r =>   TmuxTestConfig ->   Sem (TestTmuxEffects ++ r) a ->   Path Abs Dir ->