tricorder 0.7.0.0 → 1.0.0.0
raw patch · 52 files changed
+448/−519 lines, 52 filesdep ~atelier-coredep ~tricorder-typesPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: atelier-core, tricorder-types
API changes (from Hackage documentation)
- Tricorder.CLI.Arguments: parseArguments :: forall (es :: [Effect]). Arguments :> es => Eff es Command
- Tricorder.CLI.Arguments: runArguments :: forall (es :: [Effect]) a. Arguments :> es => Eff (Reader Command ': es) a -> Eff es a
- Tricorder.CLI.Operations: showTests :: forall (es :: [Effect]). (Console :> es, Exit :> es, File :> es, Reader SocketPath :> es, UnixSocket :> es) => TestOptions -> Eff es ()
- Tricorder.Session: [testMemoryLimit] :: Session -> Maybe ByteSize
- Tricorder.Session: [testTimeout] :: Session -> TestTimeout
- Tricorder.Session.Stage.Test.Config: newtype Options
- Tricorder.Session.TestTimeout: TestTimeout :: Int -> TestTimeout
- Tricorder.Session.TestTimeout: [getTestTimeout] :: TestTimeout -> Int
- Tricorder.Session.TestTimeout: instance Data.Aeson.Types.FromJSON.FromJSON Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance Data.Aeson.Types.ToJSON.ToJSON Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance Data.Default.Internal.Default Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance GHC.Classes.Eq Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance GHC.Generics.Generic Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance GHC.Show.Show Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: newtype TestTimeout
+ Tricorder.CLI.Arguments: reader :: forall (es :: [Effect]) a. Arguments :> es => Eff (Reader Command ': es) a -> Eff es a
+ Tricorder.Session.Stage.Test.Config: [memoryLimit] :: Options -> Maybe Text
+ Tricorder.Session.Stage.Test.Config: [timeoutMs] :: Options -> Maybe (AsRawUnit Millisecond)
+ Tricorder.Session.Stage.Test.Config: data Options
+ Tricorder.Session.Stage.Test.Session: [memoryLimit] :: ResolvedTestOptions -> Maybe ByteSize
+ Tricorder.Session.Stage.Test.Session: [timeout] :: ResolvedTestOptions -> Millisecond
- Tricorder.Build.Changes: CabalChangeDetected :: FilePath -> FileEvent -> CabalChangeDetected
+ Tricorder.Build.Changes: CabalChangeDetected :: FilePath -> Event -> CabalChangeDetected
- Tricorder.Build.Changes: SourceChangeDetected :: FilePath -> FileEvent -> SourceChangeDetected
+ Tricorder.Build.Changes: SourceChangeDetected :: FilePath -> Event -> SourceChangeDetected
- Tricorder.CLI.App: run :: forall (es :: [Effect]). (Brick :> es, BrickChan :> es, Clock :> es, Conc :> es, Concurrent :> es, Console :> es, Daemons :> es, Delay :> es, Exit :> es, File :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, IOE :> es, Input DaemonInfo :> es, Input Repl :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es, Process :> es, Provider_ Console Output :> es, Reader Command :> es, Reader Config :> es, Reader LogPath :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Eff es ()
+ Tricorder.CLI.App: run :: forall (es :: [Effect]). (Brick :> es, BrickChan :> es, Clock :> es, Conc :> es, Concurrent :> es, Console :> es, Daemons :> es, Delay :> es, Exit :> es, File :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, IOE :> es, Input Session :> es, Input DaemonInfo :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es, Process :> es, Provider_ Console Output :> es, Reader Command :> es, Reader Config :> es, Reader LogPath :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Eff es ()
- Tricorder.CLI.Daemon: restartDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, IOE :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Force -> Eff es (Maybe (Either [Text] Text))
+ Tricorder.CLI.Daemon: restartDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, IOE :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => IfThereArePendingQueries -> Eff es (Maybe (Either [Text] Text))
- Tricorder.CLI.Daemon: stopDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Force -> Eff es (Either [Text] Text)
+ Tricorder.CLI.Daemon: stopDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => IfThereArePendingQueries -> Eff es (Either [Text] Text)
- Tricorder.CLI.Operations: showSource :: forall (es :: [Effect]). (Console :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Repl :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es) => SourceOptions -> Eff es ()
+ Tricorder.CLI.Operations: showSource :: forall (es :: [Effect]). (Console :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Session :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es) => SourceOptions -> Eff es ()
- Tricorder.Daemon.Builder: [Consider] :: forall (a :: Type -> Type). FilePath -> FileEvent -> Builder a BuildConsideration
+ Tricorder.Daemon.Builder: [Consider] :: forall (a :: Type -> Type). FilePath -> Event -> Builder a BuildConsideration
- Tricorder.Daemon.Builder: consider :: forall (es :: [Effect]). (HasCallStack, Builder :> es) => FilePath -> FileEvent -> Eff es BuildConsideration
+ Tricorder.Daemon.Builder: consider :: forall (es :: [Effect]). (HasCallStack, Builder :> es) => FilePath -> Event -> Eff es BuildConsideration
- Tricorder.Daemon.Dispatch: dispatch :: KnownTargetNames -> Maybe LoadedModule -> FilePath -> FileEvent -> Maybe DispatchAction
+ Tricorder.Daemon.Dispatch: dispatch :: KnownTargetNames -> Maybe LoadedModule -> FilePath -> Event -> Maybe DispatchAction
- Tricorder.Daemon.TestRunner: [RunTestSuite] :: forall (a :: Type -> Type). Session -> (Suite -> a ()) -> TestTimeout -> RenderedTestCommand -> TestRunner a Suite
+ Tricorder.Daemon.TestRunner: [RunTestSuite] :: forall (a :: Type -> Type). Session -> (Suite -> a ()) -> RenderedTestCommand -> TestRunner a Suite
- Tricorder.Daemon.TestRunner: runTestSuite :: forall (es :: [Effect]). (HasCallStack, TestRunner :> es) => Session -> (Suite -> Eff es ()) -> TestTimeout -> RenderedTestCommand -> Eff es Suite
+ Tricorder.Daemon.TestRunner: runTestSuite :: forall (es :: [Effect]). (HasCallStack, TestRunner :> es) => Session -> (Suite -> Eff es ()) -> RenderedTestCommand -> Eff es Suite
- Tricorder.Daemon.Watch: WatchedFile :: FilePath -> FileEvent -> WatchedFile
+ Tricorder.Daemon.Watch: WatchedFile :: FilePath -> Event -> WatchedFile
- Tricorder.Daemon.Watch: [event] :: WatchedFile -> FileEvent
+ Tricorder.Daemon.Watch: [event] :: WatchedFile -> Event
- Tricorder.Session: Session :: Session -> Session -> Session -> Maybe ByteSize -> WatchDirs -> WatchExclusionPatterns -> ReplBuildDir -> TestTimeout -> GenerateWithHpack -> Hooks -> IdleTimeout -> Session
+ Tricorder.Session: Session :: Session -> Session -> Session -> WatchDirs -> WatchExclusionPatterns -> ReplBuildDir -> GenerateWithHpack -> Hooks -> IdleTimeout -> Session
- Tricorder.Session.Config: Config :: CommandConfig 'Build -> TestConfig -> CommandConfig 'Eval -> [FilePath] -> [Text] -> FilePath -> Int -> Bool -> Maybe Text -> Maybe Hooks -> Int -> Maybe Text -> [Text] -> Maybe [Text] -> Config
+ Tricorder.Session.Config: Config :: CommandConfig 'Build -> TestConfig -> CommandConfig 'Eval -> [FilePath] -> [Text] -> FilePath -> Maybe (Renamed "test_timeout" "test.timeout_ms") -> Maybe (Renamed "test_memory_limit" "test.memory_limit") -> Bool -> Maybe Hooks -> Int -> Maybe (Renamed "command" "build.command_template") -> Maybe (Renamed "targets" "build.targets") -> Maybe (Renamed "test_targets" "test.targets") -> Config
- Tricorder.Session.Config: [command] :: Config -> Maybe Text
+ Tricorder.Session.Config: [command] :: Config -> Maybe (Renamed "command" "build.command_template")
- Tricorder.Session.Config: [targets] :: Config -> [Text]
+ Tricorder.Session.Config: [targets] :: Config -> Maybe (Renamed "targets" "build.targets")
- Tricorder.Session.Config: [testMemoryLimit] :: Config -> Maybe Text
+ Tricorder.Session.Config: [testMemoryLimit] :: Config -> Maybe (Renamed "test_memory_limit" "test.memory_limit")
- Tricorder.Session.Config: [testTargets] :: Config -> Maybe [Text]
+ Tricorder.Session.Config: [testTargets] :: Config -> Maybe (Renamed "test_targets" "test.targets")
- Tricorder.Session.Config: [testTimeout] :: Config -> Int
+ Tricorder.Session.Config: [testTimeout] :: Config -> Maybe (Renamed "test_timeout" "test.timeout_ms")
- Tricorder.Session.Stage.Test.Command: render :: Session -> Maybe ByteSize -> TestTarget -> RenderedTestCommand
+ Tricorder.Session.Stage.Test.Command: render :: Session -> TestTarget -> RenderedTestCommand
- Tricorder.Session.Stage.Test.Config: Options :: Maybe OutputMode -> Options
+ Tricorder.Session.Stage.Test.Config: Options :: Maybe OutputMode -> Maybe (AsRawUnit Millisecond) -> Maybe Text -> Options
- Tricorder.Session.Stage.Test.Session: ResolvedTestOptions :: OutputMode -> ResolvedTestOptions
+ Tricorder.Session.Stage.Test.Session: ResolvedTestOptions :: OutputMode -> Millisecond -> Maybe ByteSize -> ResolvedTestOptions
- Tricorder.Session.Stage.Test.Session: resolve :: Repl -> [Target] -> Config -> Session
+ Tricorder.Session.Stage.Test.Session: resolve :: forall (es :: [Effect]). Log :> es => Repl -> [Target] -> Config -> Eff es Session
- Tricorder.Socket.Client: requestShutdown :: forall (es :: [Effect]). (File :> es, UnixSocket :> es) => Force -> FilePath -> Eff es (Either Text ())
+ Tricorder.Socket.Client: requestShutdown :: forall (es :: [Effect]). (File :> es, UnixSocket :> es) => IfThereArePendingQueries -> FilePath -> Eff es (Either Text ())
- Tricorder.SourceLookup: lookupModuleSource :: forall (es :: [Effect]). (FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Repl :> es, Log :> es, PackageStore :> es) => ProjectSourceDirs -> ReexportLimits -> SourceQuery -> Eff es ModuleSourceResult
+ Tricorder.SourceLookup: lookupModuleSource :: forall (es :: [Effect]). (FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Session :> es, Log :> es, PackageStore :> es) => ProjectSourceDirs -> ReexportLimits -> SourceQuery -> Eff es ModuleSourceResult
Files
- CHANGELOG.md +31/−0
- src/Tricorder/Build/Changes.hs +3/−3
- src/Tricorder/CLI/App.hs +3/−8
- src/Tricorder/CLI/App/Test.hs +9/−7
- src/Tricorder/CLI/Arguments.hs +4/−8
- src/Tricorder/CLI/Daemon.hs +10/−9
- src/Tricorder/CLI/Main.hs +31/−36
- src/Tricorder/CLI/Operations.hs +16/−86
- src/Tricorder/CLI/UI/Keys.hs +4/−3
- src/Tricorder/Config.hs +1/−1
- src/Tricorder/Daemon/Builder.hs +3/−3
- src/Tricorder/Daemon/Core.hs +6/−13
- src/Tricorder/Daemon/Dispatch.hs +12/−11
- src/Tricorder/Daemon/Ghci/Config/File.hs +2/−6
- src/Tricorder/Daemon/Ghci/Process.hs +5/−4
- src/Tricorder/Daemon/HookDispatch.hs +7/−5
- src/Tricorder/Daemon/Hpack.hs +2/−1
- src/Tricorder/Daemon/IdleTimer.hs +3/−2
- src/Tricorder/Daemon/Main.hs +25/−24
- src/Tricorder/Daemon/TestRunner.hs +19/−23
- src/Tricorder/Daemon/Watch.hs +14/−17
- src/Tricorder/Logging.hs +3/−7
- src/Tricorder/Session.hs +3/−27
- src/Tricorder/Session/CabalFile.hs +1/−1
- src/Tricorder/Session/Config.hs +10/−15
- src/Tricorder/Session/Hooks.hs +1/−1
- src/Tricorder/Session/Stage/Build/Command.hs +2/−4
- src/Tricorder/Session/Stage/Build/Session.hs +1/−1
- src/Tricorder/Session/Stage/Test/Command.hs +3/−4
- src/Tricorder/Session/Stage/Test/Config.hs +6/−1
- src/Tricorder/Session/Stage/Test/Session.hs +49/−22
- src/Tricorder/Session/State.hs +3/−2
- src/Tricorder/Session/TestTarget.hs +1/−1
- src/Tricorder/Session/TestTimeout.hs +0/−13
- src/Tricorder/Socket/Client.hs +5/−5
- src/Tricorder/Socket/Server.hs +3/−2
- src/Tricorder/SourceLookup.hs +11/−8
- src/Tricorder/SourceLookup/PackageStore.hs +3/−2
- test/Unit/Tricorder/Daemon/Ghci/ProcessSpec.hs +44/−45
- test/Unit/Tricorder/Daemon/Ghci/SessionSpec.hs +1/−1
- test/Unit/Tricorder/Daemon/TestRunnerSpec.hs +7/−12
- test/Unit/Tricorder/Daemon/WatchSpec.hs +3/−5
- test/Unit/Tricorder/Session/CabalFileSpec.hs +7/−7
- test/Unit/Tricorder/Session/ReplSpec.hs +3/−2
- test/Unit/Tricorder/Session/Stage/Build/CommandSpec.hs +3/−26
- test/Unit/Tricorder/Session/Stage/Test/CommandSpec.hs +11/−6
- test/Unit/Tricorder/Session/TestTargetSpec.hs +24/−3
- test/Unit/Tricorder/SessionSpec.hs +9/−8
- test/Unit/Tricorder/SocketSpec.hs +4/−3
- test/Unit/Tricorder/SourceLookupSpec.hs +10/−7
- test/Unit/Tricorder/WaitersSpec.hs +2/−2
- tricorder.cabal +5/−6
CHANGELOG.md view
@@ -7,6 +7,37 @@ ## [Unreleased] +## [1.0.0.0] - 2026-10-29++### Added++- Flag `--use-current` to force Tricorder to _not_ wait for the current build,+ and use the incomplete build when reporting the status. This is the inverse+ of the previous `--wait` flag.+- Flag `--prevent-daemon` to prevent Tricorder from starting the daemon if it+ is not already running. This is the inverse of the previous `--start-daemon`+ flag.++### Changed++- Moved config option `session.test_timeout` to `session.test.timeout_ms`.+- Moved config option `session.test_memory_limit` to `session.test.memory_limit`.+- Renamed flag `--force` to `--ignore-pending-queries`.++### Removed++- Removed deprecated config option `session.command`.+- Removed deprecated config option `session.targets`.+- Removed deprecated config option `session.test_targets`.+- Removed deprecated subcommand `test-results`.+- Removed deprecated subcommand `start`.+- Removed deprecated subcommand `stop`.+- Removed deprecated subcommand `restart`.+- Removed flag `--wait`. Tricorder now waits for the current build to finish by+ default. Use `--use-current` to keep the old default behavior.+- Removed flag `--start-daemon`. Tricorder now starts the daemon by default.+ Use `--prevent-daemon` to keep the old default behavior.+ ## [0.7.0.0] - 2026-10-23 ### Added
src/Tricorder/Build/Changes.hs view
@@ -5,7 +5,7 @@ ) where -import Atelier.Effects.FileWatcher (FileEvent)+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event) -- | Classifies what kind of file change triggered a dirty signal.@@ -14,9 +14,9 @@ data ChangeKind = SourceChange | CabalChange deriving stock (Eq, Ord, Show) -data CabalChangeDetected = CabalChangeDetected FilePath FileEvent+data CabalChangeDetected = CabalChangeDetected FilePath FileWatcher.Event deriving stock (Eq, Show) -data SourceChangeDetected = SourceChangeDetected FilePath FileEvent+data SourceChangeDetected = SourceChangeDetected FilePath FileWatcher.Event deriving stock (Eq, Show)
src/Tricorder/CLI/App.hs view
@@ -28,14 +28,13 @@ import Tricorder.API.IfDaemonIsStopped qualified as IfDaemonIsStopped import Tricorder.CLI.Daemon (startDaemon, waitForDaemon)-import Tricorder.CLI.Operations (showEvalComments, showLog, showSource, showStatus, showTests)+import Tricorder.CLI.Operations (showEvalComments, showLog, showSource, showStatus) import Tricorder.CLI.UI (viewUi) import Tricorder.CLI.UI.Brick (Brick) import Tricorder.CLI.UI.BrickChan (BrickChan) import Tricorder.Daemon.DaemonInfo (DaemonInfo) import Tricorder.Runtime (LogPath (..), PidFile (..), SocketPath (..)) import Tricorder.Session.CabalFile (CabalFile)-import Tricorder.Session.Repl (Repl) import Tricorder.Socket.Client (isDaemonRunning) import Tricorder.Socket.UnixSocket (UnixSocket) import Tricorder.SourceLookup.GhcPkg (GhcPkg)@@ -45,6 +44,7 @@ import Tricorder.CLI.App.Daemon qualified as Daemon import Tricorder.CLI.App.Test qualified as Test import Tricorder.CLI.UI.Keys qualified as Keys+import Tricorder.Session.Stage.Build.Session qualified as Build (Session) run@@ -62,8 +62,8 @@ , GhcPkg :> es , Hackage :> es , IOE :> es+ , Input Build.Session :> es , Input DaemonInfo :> es- , Input Repl :> es , Input [CabalFile] :> es , Log :> es , PackageStore :> es@@ -89,11 +89,6 @@ showStatus opts | not running -> Console.putStrLn "Stopped." | otherwise -> showStatus opts- TestResults opts -> do- running <- isDaemonRunning- if not running- then Console.putStrLn "Stopped."- else showTests opts Log logMode -> do logFile <- asks @LogPath (.getLogPath) case logMode of
src/Tricorder/CLI/App/Test.hs view
@@ -2,19 +2,21 @@ import Atelier.Effects.Console (Console) import Atelier.Effects.Delay (Delay)-import Atelier.Effects.Exit (Exit, exitFailure)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.File (File) import Atelier.Effects.Posix.Daemons (Daemons, PidFile) import Effectful (IOE) import Effectful.Reader.Static (Reader)+import Tricorder.API.IfBuildIsPending (IfBuildIsPending (..)) import Tricorder.API.IfDaemonIsStopped (IfDaemonIsStopped) import Tricorder.API.OutputFormat (OutputFormat (..)) import Tricorder.API.Test (Command (..), SuitesOptions (..))-import Tricorder.API.WaitMode (WaitMode (..)) import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Exit qualified as Exit import Effectful.Reader.Static qualified as Reader import Tricorder.API.FieldMask.Schema qualified as FieldMask.Schema+import Tricorder.API.IfBuildIsPending qualified as IfBuildIsPending import Tricorder.API.IfDaemonIsStopped qualified as IfDaemonIsStopped import Tricorder.API.Test.SuiteView.Schema qualified as SuiteView.Schema @@ -51,7 +53,7 @@ printResult = \case Left err -> do Console.putTextLn err- exitFailure+ Exit.failure Right res -> Console.putTextLn res @@ -73,7 +75,7 @@ JsonOutput -> Json.argumentError err Right (targets, selection) -> ensureDaemonRunning opts.ifDaemonIsStopped do- result <- awaitBuildStatus opts.wait+ result <- awaitBuildStatus opts.ifBuildIsPending case opts.format of TextOutput -> pure@@ -106,12 +108,12 @@ , Reader SocketPath :> es , UnixSocket :> es )- => WaitMode -> Eff es (Either Text BuildState)+ => IfBuildIsPending -> Eff es (Either Text BuildState) awaitBuildStatus wait = do SocketPath sockPath <- Reader.ask case wait of- WaitForBuild -> queryStatusWait sockPath- ShowCurrent -> queryStatus sockPath+ IfBuildIsPending.WaitForBuild -> queryStatusWait sockPath+ IfBuildIsPending.UseCurrent -> queryStatus sockPath ensureDaemonRunning
src/Tricorder/CLI/Arguments.hs view
@@ -1,4 +1,4 @@-module Tricorder.CLI.Arguments (runArguments, parseArguments) where+module Tricorder.CLI.Arguments (reader) where import Atelier.Effects.Arguments (Arguments) import Effectful.Reader.Static (Reader)@@ -13,14 +13,10 @@ import Tricorder.Version qualified as Version -runArguments :: (Arguments :> es) => Eff (Reader Command : es) a -> Eff es a-runArguments eff = do- args <- parseArguments+reader :: (Arguments :> es) => Eff (Reader Command : es) a -> Eff es a+reader eff = do+ args <- Args.execParser opts Reader.runReader args eff---parseArguments :: (Arguments :> es) => Eff es Command-parseArguments = Args.execParser opts opts :: ParserInfo Command
src/Tricorder/CLI/Daemon.hs view
@@ -9,18 +9,19 @@ import Atelier.Effects.Delay (Delay) import Atelier.Effects.File (File) import Atelier.Effects.Posix.Daemons (Daemons)-import Atelier.Effects.Timeout (Timeout, timeout)+import Atelier.Effects.Timeout (Timeout) import Atelier.Time (Millisecond, Second) import Effectful (IOE) import Effectful.NonDet (OnEmptyPolicy (..), emptyEff, runNonDet) import Effectful.Reader.Static (Reader, ask) import Effectful.Writer.Static.Local (runWriter, tell)-import Tricorder.API.Force (Force)+import Tricorder.API.IfThereArePendingQueries (IfThereArePendingQueries) import Prelude hiding (force) import Atelier.Effects.Delay qualified as Delay import Atelier.Effects.Posix.Daemons qualified as Daemons-import Tricorder.API.Force qualified as Force+import Atelier.Effects.Timeout qualified as Timeout+import Tricorder.API.IfThereArePendingQueries qualified as IfThereArePendingQueries import Tricorder.Runtime (PidFile, SocketPath (..)) import Tricorder.Socket.Client (isDaemonReady, isDaemonRunning, requestShutdown)@@ -52,7 +53,7 @@ , Timeout :> es , UnixSocket :> es )- => Force -> Eff es (Either [Text] Text)+ => IfThereArePendingQueries -> Eff es (Either [Text] Text) stopDaemon force = do SocketPath sockPath <- ask pidFile <- ask@@ -67,11 +68,11 @@ (Nothing, es) -> pure $ Left es where timeoutDelay :: Second = case force of- Force.Force -> 3- Force.NoForce -> 6+ IfThereArePendingQueries.DontWait -> 3+ IfThereArePendingQueries.Wait -> 6 requestStop sockPath pidFile = do timeout1second (requestShutdown force sockPath) >>= \_ -> do- didStop <- fmap isJust $ timeout timeoutDelay $ waitForStop pidFile+ didStop <- fmap isJust $ Timeout.with timeoutDelay $ waitForStop pidFile if didStop then pure "Daemon stopped."@@ -86,7 +87,7 @@ tell ["Daemon did not respond to SIGKILL: " <> show ex] emptyEff - timeout1second = fmap (join . fmap rightToMaybe) . timeout (1 :: Second)+ timeout1second = fmap (join . fmap rightToMaybe) . Timeout.with (1 :: Second) waitForStop :: forall es'. (Daemons :> es', Delay :> es') => PidFile -> Eff es' () waitForStop pidFile = fix \rec -> do@@ -113,7 +114,7 @@ , Timeout :> es , UnixSocket :> es )- => Force+ => IfThereArePendingQueries -> Eff es (Maybe (Either [Text] Text)) restartDaemon force = do running <- isDaemonRunning
src/Tricorder/CLI/Main.hs view
@@ -1,38 +1,36 @@ module Tricorder.CLI.Main (main) where -import Atelier.Effects.Arguments (runArgumentsIO)-import Atelier.Effects.Clock (runClock)-import Atelier.Effects.Conc (runConc)-import Atelier.Effects.Console (Console, runConsole, runConsoleOut)-import Atelier.Effects.Delay (runDelay)-import Atelier.Effects.Env (runEnv)-import Atelier.Effects.Exit (exitFailure, runExit)-import Atelier.Effects.File (runFile)-import Atelier.Effects.FileSystem (runFileSystemIO)-import Atelier.Effects.Log (runLogNoOp)-import Atelier.Effects.Posix.Daemons (runDaemons)-import Atelier.Effects.Process (runProcessIO)-import Atelier.Effects.Timeout (runTimeout)+import Atelier.Effects.Console (Console) import Atelier.Signal (installTerminationHandler) import Data.Default (def) import Effectful (runEff) import Effectful.Concurrent (runConcurrent) import Effectful.Provider (provideWith_, runProvider_) +import Atelier.Effects.Arguments qualified as Arguments+import Atelier.Effects.Clock qualified as Clock+import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Delay qualified as Delay+import Atelier.Effects.Env qualified as Env+import Atelier.Effects.Exit qualified as Exit+import Atelier.Effects.File qualified as File+import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileSystem.Glob qualified as Glob import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log+import Atelier.Effects.Posix.Daemons qualified as Daemons+import Atelier.Effects.Process qualified as Process+import Atelier.Effects.Timeout qualified as Timeout -import Tricorder.CLI.Arguments (runArguments) import Tricorder.CLI.UI.Brick (runBrick) import Tricorder.CLI.UI.BrickChan (runBrickChan) import Tricorder.Config (asInput) import Tricorder.Runtime (runLogPath, runPidFile, runProjectRoot, runRuntimeDir, runSocketPath) import Tricorder.Session.CabalFile (inputCabalFiles)-import Tricorder.Session.CommandTemplate (CommandTemplate (..))-import Tricorder.Session.Stage.Build.Session (Session (..)) import Tricorder.CLI.App qualified as App+import Tricorder.CLI.Arguments qualified as CLI.Arguments import Tricorder.CLI.UI.Keys qualified as Keys import Tricorder.Config.LoadedConfig qualified as LoadedConfig import Tricorder.Daemon.DaemonInfo qualified as DaemonInfo@@ -47,18 +45,18 @@ main :: IO () main = runEff- . runTimeout+ . Timeout.run . runConcurrent- . runConc+ . Conc.runIO . runBrickChan . runBrick- . runConsole- . runProvider_ @Console @Console.Output runConsoleOut- . runExit- . runClock- . runDelay- . runFile- . runFileSystemIO+ . Console.runIO+ . runProvider_ @Console @Console.Output Console.runOut+ . Exit.runIO+ . Clock.runIO+ . Delay.runIO+ . File.run+ . FileSystem.runIO . Glob.runIO . runProjectRoot . runRuntimeDir@@ -69,13 +67,13 @@ . LoadedConfig.asNestedInput @"keybindings" @Keys.Config . Input.map (either @_ @Keys.Config (const def) id) . Input.toReader @Keys.Config- . runDaemons- . runProcessIO- . runArgumentsIO- . runArguments+ . Daemons.run+ . Process.run+ . Arguments.runIO+ . CLI.Arguments.reader . UnixSocket.runIO- . runEnv- . runLogNoOp+ . Env.runIO+ . Log.runNoOp . StackProject.inputWithCache . inputCabalFiles . Tricorder.Config.asInput@@ -85,13 +83,10 @@ provideWith_ @Console Console.Stderr $ Console.putTextLn $ "Failed to load session: " <> err- exitFailure+ Exit.failure Right session -> Input.withConst session- -- TODO: Make whatever uses this 'Repl' value use it- -- more sensibly. Arbitrarily using the build stage's- -- 'Repl' doesn't seem right.- . Input.withConst session.buildSession.commandTemplate.repl+ . Input.withEff (Session.buildSession <$> Input.input) . GhcPkg.runGhcPkgIO . PackageStore.run . Hackage.run
src/Tricorder/CLI/Operations.hs view
@@ -2,7 +2,6 @@ ( showLog , showSource , showStatus- , showTests , showEvalComments , showDaemonInfo )@@ -10,7 +9,7 @@ import Atelier.Effects.Clock (Clock, currentTimeZone) import Atelier.Effects.Console (Console)-import Atelier.Effects.Exit (Exit, exitFailure)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.File (File) import Atelier.Effects.FileSystem (FileSystem, doesFileExist, readFileLbs) import Atelier.Effects.Input (Input, input)@@ -24,30 +23,26 @@ ( EvalCommentsOptions (..) , SourceOptions (..) , StatusOptions (..)- , TestOptions (..) , Verbosity (..) )+import Tricorder.API.IfBuildIsPending (IfBuildIsPending (..)) import Tricorder.API.OutputFormat (OutputFormat (..))-import Tricorder.API.WaitMode (WaitMode (..)) import Tricorder.SourceLookup.ReexportLimits (ReexportLimits (..), defaultReexportLimits) import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Exit qualified as Exit import Data.ByteString.Lazy qualified as BSL import Data.Map.Strict qualified as Map import Data.Text qualified as T+import Tricorder.API.IfBuildIsPending qualified as IfBuildIsPending import Tricorder.Build (BuildState (..), Severity (..)) import Tricorder.Build.Duration (Duration (..)) import Tricorder.Build.Test (Suites (..))-import Tricorder.CLI.Render- ( diagnosticLineIndexed- , formatDuration- , renderSourceResults- )+import Tricorder.CLI.Render (diagnosticLineIndexed, formatDuration, renderSourceResults) import Tricorder.Daemon.DaemonInfo (DaemonInfo (..)) import Tricorder.Runtime (SocketPath (..)) import Tricorder.Session.CabalFile (CabalFile)-import Tricorder.Session.Repl (Repl) import Tricorder.Socket.Client (queryStatus, queryStatusWait) import Tricorder.Socket.UnixSocket (UnixSocket) import Tricorder.SourceLookup (lookupModuleSource, projectSourceDirs)@@ -60,6 +55,7 @@ import Tricorder.Build.EvalComment qualified as Eval import Tricorder.Build.Test qualified as Test import Tricorder.Build.Test qualified as Tests+import Tricorder.Session.Stage.Build.Session qualified as Build (Session) import Tricorder.Session.Target qualified as Target import Tricorder.Session.TestTarget qualified as TestTarget @@ -69,7 +65,7 @@ reportBuildFailed msg = do Console.putTextLn "Build command failed:" Console.putTextLn msg- exitFailure+ Exit.failure showStatus@@ -139,7 +135,7 @@ mapM_ printDiag (zip [1 ..] result.diagnostics) Console.putTextLn $ buildSummary tz result mapM_ (uncurry (printTestRun verbosity)) $ Map.toList postBuild.testSuites.getSuites- when (buildHasErrors result || Test.hasFailedTests postBuild.testSuites) exitFailure+ when (buildHasErrors result || Test.hasFailedTests postBuild.testSuites) Exit.failure printTestRun verbosity tgt tr = do Console.putTextLn $ case tr of@@ -200,78 +196,12 @@ else readFileLbs path >>= Console.putStr . BSL.toStrict -showTests- :: ( Console :> es- , Exit :> es- , File :> es- , Reader SocketPath :> es- , UnixSocket :> es- )- => TestOptions -> Eff es ()-showTests opts = do- result <- awaitBuildStatus opts.wait- case result of- Left err -> Console.putTextLn $ "Error: " <> err- Right state ->- case state.phase of- Build.Starting -> Console.putStrLn "Daemon starting, no test results yet."- Build.Building _ _ -> Console.putStrLn "Build in progress, no test results yet."- Build.PostBuilding _ postBuild -> renderTestRuns postBuild.testSuites.getSuites- Build.Finished _ postBuild -> renderTestRuns postBuild.testSuites.getSuites- Build.Failed msg -> reportBuildFailed msg- where- renderTestRuns suites- | Map.null suites = Console.putStrLn "No test results."- | Map.null filteredSuites = do- Console.putStrLn "All passed."- mapM_ (Console.putTextLn . (" " <>) . TestTarget.render) $ Map.keys suites- | otherwise = do- mapM_ (uncurry printTestOutput) $ Map.toList filteredSuites- when (any Test.isFailedRun filteredSuites) exitFailure- where- filteredSuites =- if opts.failedOnly- then- Map.filter Test.isFailedRun suites- else- suites-- printTestOutput tgt tr = case tr of- Test.SuiteRunning Nothing ->- Console.putTextLn $ t <> "running..."- Test.SuiteRunning (Just p) ->- Console.putTextLn $ t <> "running... (" <> show p.compiled <> "/" <> show p.total <> ")"- Test.SuiteErrored e ->- Console.putTextLn $ t <> "error: " <> e.message- Test.SuiteCompleted c -> do- Console.putTextLn $ t <> completionSummary c- if opts.failedOnly- then- if null c.testCases- then do- Console.putTextLn " (unrecognised test runner format — showing full output)"- mapM_ (Console.putTextLn . (" " <>)) (stripGhciNoise (lines c.output))- else- mapM_ printFailedCase (filter Test.caseFailed c.testCases)- else- mapM_ (Console.putTextLn . (" " <>)) (stripGhciNoise (lines c.output))- where- t = TestTarget.render tgt <> " "-- printFailedCase tc = do- Console.putTextLn $ " " <> tc.description- case tc.outcome of- Test.Failed details ->- mapM_ (Console.putTextLn . (" " <>)) (T.lines details)- Test.Passed -> pure ()-- showSource :: ( Console :> es , FileSystem :> es , GhcPkg :> es , Hackage :> es- , Input Repl :> es+ , Input Build.Session :> es , Input [CabalFile] :> es , Log :> es , PackageStore :> es@@ -332,7 +262,7 @@ displayTextOutput = \case Left err -> do Console.putTextLn $ "Error: " <> err- exitFailure+ Exit.failure Right (BuildState phase _) -> case phase of Build.Starting -> Console.putStrLn "Starting..." Build.Building _ _ -> Console.putStrLn "Building..."@@ -340,7 +270,7 @@ Build.Finished _ postBuild -> txtEvalComments postBuild Build.Failed err -> do Console.putTextLn $ "Error: " <> err- exitFailure+ Exit.failure where txtEvalComments postBuild = let evalutatingComments = Eval.phasePending postBuild.evalComments@@ -387,13 +317,13 @@ displayJsonOutput = \case Left err -> do putJson $ Eval.Failed err- exitFailure+ Exit.failure Right (BuildState phase _) -> case phase of Build.Starting -> putJson Eval.Starting Build.Building _ _ -> putJson Eval.Building Build.Failed msg -> do putJson $ Eval.Failed msg- exitFailure+ Exit.failure Build.PostBuilding _ postBuild -> jsonEvalComments postBuild Build.Finished _ postBuild -> jsonEvalComments postBuild where@@ -444,9 +374,9 @@ , Reader SocketPath :> es , UnixSocket :> es )- => WaitMode -> Eff es (Either Text BuildState)+ => IfBuildIsPending -> Eff es (Either Text BuildState) awaitBuildStatus wait = do SocketPath sockPath <- ask case wait of- WaitForBuild -> queryStatusWait sockPath- ShowCurrent -> queryStatus sockPath+ IfBuildIsPending.WaitForBuild -> queryStatusWait sockPath+ IfBuildIsPending.UseCurrent -> queryStatus sockPath
src/Tricorder/CLI/UI/Keys.hs view
@@ -9,7 +9,7 @@ where import Atelier.Effects.Console (Console)-import Atelier.Effects.Exit (Exit, exitFailure)+import Atelier.Effects.Exit (Exit) import Brick ( EventM , Widget@@ -49,6 +49,7 @@ import Text.Casing (quietSnake) import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Exit qualified as Exit import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text qualified as T@@ -154,7 +155,7 @@ unless (null errors) do Console.putTextLn "Error(s) encountered when attempting to parse key bindings:" traverse_ (Console.putTextLn . toText) errors- exitFailure+ Exit.failure pure customBindings @@ -228,7 +229,7 @@ $ T.intercalate "\n\n" $ "Your key bindings have collisions:" : (uncurry showCollision <$> collisions)- exitFailure+ Exit.failure Right dispatcher -> pure dispatcher where showCollision binding' handlers =
src/Tricorder/Config.hs view
@@ -6,7 +6,7 @@ import Atelier.Effects.Input (Input) import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults (WithDefaults (..))+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON, ToJSON) import Data.Default (Default (..))
src/Tricorder/Daemon/Builder.hs view
@@ -11,7 +11,6 @@ where import Atelier.Effects.Clock (Clock, UTCTime)-import Atelier.Effects.FileWatcher (FileEvent) import Atelier.Effects.Log (Log) import Atelier.Effects.PubSub.Pub (Pub) import Atelier.Time (nominalDiffTime)@@ -25,6 +24,7 @@ import System.FilePath (normalise) import Atelier.Effects.Clock qualified as Clock+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event) import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub.Pub qualified as Pub import Data.Map.Strict qualified as Map@@ -58,7 +58,7 @@ data Builder :: Effect where- Consider :: FilePath -> FileEvent -> Builder m BuildConsideration+ Consider :: FilePath -> FileWatcher.Event -> Builder m BuildConsideration Build :: DispatchAction -> Builder m (Either BuildFailure NewLoadResult) @@ -127,7 +127,7 @@ considerBuild :: (State BuilderState :> es) => FilePath- -> FileEvent+ -> FileWatcher.Event -> Eff es BuildConsideration considerBuild fp event = do builderState <- get
src/Tricorder/Daemon/Core.hs view
@@ -3,7 +3,6 @@ import Atelier.Effects.Chan (Chan) import Atelier.Effects.Clock (Clock) import Atelier.Effects.Conc (Conc)-import Atelier.Effects.FileWatcher (FileEvent) import Atelier.Effects.Log (Log) import Atelier.Effects.Process (Process) import Atelier.Effects.PubSub.Pub (Pub)@@ -15,6 +14,7 @@ import Relude.Extra.Tuple (dup) import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event) import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub qualified as PubSub import Atelier.Effects.PubSub.Pub qualified as Pub@@ -24,7 +24,6 @@ import Effectful.State.Static.Shared qualified as State import Tricorder.Build (BuildId, BuildPhase, BuildResult, PostBuild (..), Severity (..))-import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Build.Changes (CabalChangeDetected (..), SourceChangeDetected (..)) import Tricorder.Daemon.Builder ( BuildConsideration (..)@@ -40,7 +39,6 @@ import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session (Session (..)) import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..))-import Tricorder.Session.TestTimeout (TestTimeout (..)) import Tricorder.Waiters (Waiters) import Tricorder.Build qualified as Build@@ -66,7 +64,7 @@ data RestartBuilder = RestartBuilder -data ReloadBuilder = ReloadBuilder FilePath FileEvent+data ReloadBuilder = ReloadBuilder FilePath FileWatcher.Event -- | Top of the build loop. Responsible for handling changes to the Tricorder@@ -282,10 +280,7 @@ => Session -> BuildResult -> Eff es Test.Suites runTests session buildResult | hasTargets session.testSession.targets && noErrors buildResult.diagnostics =- runTestsForTargets- session.testSession- session.testMemoryLimit- session.testTimeout+ runTestsForTargets session.testSession | otherwise = pure mempty where hasTargets = not . null@@ -298,10 +293,8 @@ , TestRunner :> es ) => Test.Session- -> Maybe ByteSize- -> TestTimeout -> Eff es Test.Suites-runTestsForTargets testSession memoryLimit testTimeout = do+runTestsForTargets testSession = do Pub.publish $ Test.Suites initial Log.info $ "Running " <> show (length testSession.targets) <> " test suite(s)" fmap Test.Suites . State.execState initial $ traverse_ go testSession.targets@@ -309,7 +302,7 @@ initial = Map.fromList $ (,Test.SuiteRunning Nothing) <$> testSession.targets go target = do Log.info $ "Running tests: " <> renderedTarget- let testCommand = Test.Command.render testSession memoryLimit target+ let testCommand = Test.Command.render testSession target publishProgress suite = do updated <- State.state $ dup . Map.insert target suite Pub.publish $ Test.Suites updated@@ -318,7 +311,7 @@ <> renderedTarget <> " command:\n" <> show testCommand.command- finishedSuite <- TestRunner.runTestSuite testSession publishProgress testTimeout testCommand+ finishedSuite <- TestRunner.runTestSuite testSession publishProgress testCommand case finishedSuite of Test.SuiteErrored (Test.SuiteError message) -> Log.warn $ "Test suite " <> renderedTarget <> "failed: " <> message
src/Tricorder/Daemon/Dispatch.hs view
@@ -12,9 +12,10 @@ ) where -import Atelier.Effects.FileWatcher (FileEvent (..)) import System.FilePath (isAbsolute, normalise, splitDirectories, takeExtension, (</>)) +import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event)+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher.Event import Data.List qualified as List import Data.Map.Strict qualified as Map import Data.Set qualified as Set@@ -126,22 +127,22 @@ :: KnownTargetNames -> Maybe Ghci.LoadedModule -> FilePath- -> FileEvent+ -> FileWatcher.Event -> Maybe DispatchAction dispatch knownTargets known fp event = case known of Just lm -> Just $ case event of- Added -> Reload- Modified -> Reload- Removed -> Unadd lm.moduleName+ FileWatcher.Event.Added -> Reload+ FileWatcher.Event.Modified -> Reload+ FileWatcher.Event.Removed -> Unadd lm.moduleName Nothing | fileMatchesAnyTarget knownTargets fp -> case event of- Added -> Just Reload- Modified -> Just Reload- Removed -> Nothing+ FileWatcher.Event.Added -> Just Reload+ FileWatcher.Event.Modified -> Just Reload+ FileWatcher.Event.Removed -> Nothing | otherwise -> case event of- Added -> Just (Add fp)- Modified -> Just (Add fp)- Removed -> Nothing+ FileWatcher.Event.Added -> Just (Add fp)+ FileWatcher.Event.Modified -> Just (Add fp)+ FileWatcher.Event.Removed -> Nothing -- | Keep only diagnostics whose file is under one of the watched directories.
src/Tricorder/Daemon/Ghci/Config/File.hs view
@@ -6,17 +6,13 @@ import Atelier.Time (AsRawUnit (..), Millisecond) import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults- ( WithDefaults (..)- )+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON, ToJSON) import Data.Default (Default (..)) import Atelier.Time qualified as Time -import Tricorder.Daemon.Ghci.Config- ( Config (..)- )+import Tricorder.Daemon.Ghci.Config (Config (..)) data File = File
src/Tricorder/Daemon/Ghci/Process.hs view
@@ -30,7 +30,7 @@ , setWorkingDir , shell )-import Atelier.Effects.Timeout (Timeout, timeout)+import Atelier.Effects.Timeout (Timeout) import Control.Concurrent.STM (TVar, modifyTVar', readTVar, retry, writeTVar) import Data.Sequence ((|>)) import Data.Time.Units (Second)@@ -43,6 +43,7 @@ import Atelier.Effects.Log qualified as Log import Atelier.Effects.Process qualified as Atelier import Atelier.Effects.Process qualified as Process+import Atelier.Effects.Timeout qualified as Timeout import Data.Text qualified as T import Tricorder.Daemon.Ghci.Config (Config (..))@@ -289,7 +290,7 @@ void $ trySync $ do File.hPutTextLn ghciProcess.stdin ":quit" File.hFlush ghciProcess.stdin- void $ timeout config.shutdownTimeout (Process.waitExitCode ghciProcess.handle)+ void $ Timeout.with config.shutdownTimeout (Process.waitExitCode ghciProcess.handle) -- ---------------------------------------------------------------------------@@ -407,7 +408,7 @@ Conc.scoped do stderrThread <- Conc.fork drainStderr- result <- timeout delay $ trySync watchStdout+ result <- Timeout.with delay $ trySync watchStdout case result of Just (Right ()) -> pure () Just (Left ex) -> do@@ -416,7 +417,7 @@ -- pipe lingers) so we surface its *complete* output — e.g. -- cabal's dependency-resolution error — rather than whatever -- the drain happened to have read so far.- _ <- timeout (1 :: Second) (Conc.await stderrThread)+ _ <- Timeout.with (1 :: Second) (Conc.await stderrThread) captured <- atomically $ readTVar capturedVar throwIO $ StartupFailed $ fromMaybe (startupExitedMessage ex) (renderCapturedLines captured) Nothing -> do
src/Tricorder/Daemon/HookDispatch.hs view
@@ -1,10 +1,11 @@ module Tricorder.Daemon.HookDispatch (main) where -import Atelier.Effects.FileWatcher (FileEvent (..)) import Atelier.Effects.Process (Process) import Atelier.Effects.PubSub.Sub (Sub) import Effectful.Reader.Static (Reader) +import Atelier.Effects.FileWatcher.Event qualified as FileWatcher+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher.Event import Atelier.Effects.PubSub.Sub qualified as Sub import System.FilePath qualified as Path @@ -27,8 +28,9 @@ | otherwise -> pure () -runHsHook :: (Process :> es, Reader ProjectRoot :> es) => HaskellHooks -> FileEvent -> Eff es ()+runHsHook+ :: (Process :> es, Reader ProjectRoot :> es) => HaskellHooks -> FileWatcher.Event -> Eff es () runHsHook hooks = \case- Added -> whenJust hooks.fileAdded Hook.run- Modified -> whenJust hooks.fileModified Hook.run- Removed -> whenJust hooks.fileRemoved Hook.run+ FileWatcher.Event.Added -> whenJust hooks.fileAdded Hook.run+ FileWatcher.Event.Modified -> whenJust hooks.fileModified Hook.run+ FileWatcher.Event.Removed -> whenJust hooks.fileRemoved Hook.run
src/Tricorder/Daemon/Hpack.hs view
@@ -11,6 +11,7 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.FileWatcher qualified as FileWatcher+import Atelier.Effects.FileWatcher.Watch qualified as FileWatcher.Watch import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub qualified as PubSub import Atelier.Effects.PubSub.Pub qualified as Pub@@ -73,7 +74,7 @@ => ProjectRoot -> Eff es Void watchPackageYaml root = do FileWatcher.watchFilePathsDebounced- [FileWatcher.dirWhere root.getProjectRoot (packageYaml `List.isSuffixOf`)]+ [FileWatcher.Watch.dirWhere root.getProjectRoot (packageYaml `List.isSuffixOf`)] \path _ -> Pub.publish $ RePack path
src/Tricorder/Daemon/IdleTimer.hs view
@@ -8,7 +8,7 @@ import Atelier.Effects.Clock (Clock, currentTime) import Atelier.Effects.Conc (Conc) import Atelier.Effects.Delay (Delay)-import Atelier.Effects.Exit (Exit, exitSuccess)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.Input (Input, input) import Atelier.Effects.Log (Log) import Atelier.Time (Second, nominalDiffTime)@@ -22,6 +22,7 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.Delay qualified as Delay+import Atelier.Effects.Exit qualified as Exit import Atelier.Effects.Log qualified as Log import Tricorder.Session.IdleTimeout (IdleTimeout (..))@@ -78,7 +79,7 @@ $ "Idle for " <> show secs <> " with no active connections, shutting down."- exitSuccess+ Exit.success interpretWith act \env -> \case WithActivity action -> do
src/Tricorder/Daemon/Main.hs view
@@ -1,29 +1,29 @@ module Tricorder.Daemon.Main (main) where -import Atelier.Effects.Chan (runChan)-import Atelier.Effects.Clock (runClock)-import Atelier.Effects.Conc (runConc)-import Atelier.Effects.Debounce (runDebounce)-import Atelier.Effects.Delay (runDelay)-import Atelier.Effects.Env (runEnv)-import Atelier.Effects.Exit (runExit)-import Atelier.Effects.File (runFile)-import Atelier.Effects.FileSystem (runFileSystemIO)-import Atelier.Effects.FileWatcher (runFileWatcherIO)-import Atelier.Effects.Process (runProcessIO)-import Atelier.Effects.Timeout (runTimeout) import Atelier.Signal (installTerminationHandler) import Data.Default (def) import Effectful (runEff) import Effectful.Concurrent (runConcurrent) +import Atelier.Effects.Chan qualified as Chan+import Atelier.Effects.Clock qualified as Clock import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.Debounce qualified as Debounce+import Atelier.Effects.Delay qualified as Delay+import Atelier.Effects.Env qualified as Env+import Atelier.Effects.Exit qualified as Exit+import Atelier.Effects.FSNotify qualified as FSNotify+import Atelier.Effects.File qualified as File+import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileSystem.Glob qualified as Glob+import Atelier.Effects.FileWatcher qualified as FileWatcher import Atelier.Effects.Input qualified as Input import Atelier.Effects.Log qualified as Log+import Atelier.Effects.Process qualified as Process import Atelier.Effects.PubSub qualified as PubSub import Atelier.Effects.PubSub.Pub qualified as Pub import Atelier.Effects.PubSub.Sub qualified as Sub+import Atelier.Effects.Timeout qualified as Timeout import Effectful.Reader.Static qualified as Reader import Effectful.State.Static.Shared qualified as State @@ -62,27 +62,28 @@ main = runEff . runConcurrent- . runConc- . runClock- . runDelay- . runTimeout- . runDebounce @FilePath- . runFileWatcherIO- . runFileSystemIO+ . Conc.runIO+ . Clock.runIO+ . Delay.runIO+ . Timeout.run+ . Debounce.run @FilePath+ . FileSystem.runIO+ . FSNotify.run+ . FileWatcher.runIO . Glob.runIO . runProjectRoot- . runExit- . runFile+ . Exit.runIO+ . File.run . runRuntimeDir . runSocketPath . runLogPath- . runEnv+ . Env.runIO . LoadedConfig.asInput . runLogging- . runChan+ . Chan.run . StackProject.inputWithCache . inputCabalFiles- . runProcessIO+ . Process.run . runGhcPkgIO . UnixSocket.runIO . Ghci.Session.run
src/Tricorder/Daemon/TestRunner.hs view
@@ -31,7 +31,7 @@ , setWorkingDir , shell )-import Atelier.Effects.Timeout (Timeout, timeout)+import Atelier.Effects.Timeout (Timeout) import Control.Concurrent.STM (modifyTVar') import Control.Exception (throwIO) import Data.Sequence ((|>))@@ -49,6 +49,8 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.File qualified as File import Atelier.Effects.Process qualified as Process+import Atelier.Effects.Timeout qualified as Timeout+import Atelier.Time qualified as Time import Data.List qualified as List import Data.Text qualified as T @@ -59,7 +61,6 @@ import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..)) import Tricorder.Session.Stage.Test.Config (OutputMode (..)) import Tricorder.Session.Stage.Test.Session (ResolvedTestOptions (..), Session (..))-import Tricorder.Session.TestTimeout (TestTimeout (..)) import Tricorder.TestOutput (parseHspecDuration, parseHspecOutput) import Tricorder.Build.Test qualified as Test@@ -75,7 +76,6 @@ :: Session -> (Test.Suite -> m ()) -- ^ Handler for test run progress- -> TestTimeout -> RenderedTestCommand -> TestRunner m Test.Suite @@ -98,12 +98,12 @@ => Eff (TestRunner : es) a -> Eff es a run act = do interpretWith act \env -> \case- RunTestSuite session progressHandler testTimeout cmd ->+ RunTestSuite session progressHandler cmd -> localUnlift env (ConcUnlift Persistent Unlimited) \unlift -> do let onProgress = unlift . progressHandler . loadingToProgress case cmd.options.outputMode of- ReplOutput -> runTestSuiteWithGHCi session onProgress testTimeout cmd.command- StdoutOutput -> runTestSuiteWithStdout onProgress testTimeout cmd.command+ ReplOutput -> runTestSuiteWithGHCi session onProgress cmd.command+ StdoutOutput -> runTestSuiteWithStdout session onProgress cmd.command runTestSuiteWithStdout@@ -114,11 +114,11 @@ , Reader ProjectRoot :> es , Timeout :> es )- => (Ghci.Loading -> Eff es ())- -> TestTimeout+ => Session+ -> (Ghci.Loading -> Eff es ()) -> RenderedCommand 'Test -> Eff es Test.Suite-runTestSuiteWithStdout onProgress testTimeout cmd = do+runTestSuiteWithStdout session onProgress cmd = do ProjectRoot projectRoot <- ask -- Lines from both streams, in the order they arrived. outputVar <- newTVarIO (mempty :: Seq Text)@@ -149,11 +149,9 @@ result <- trySync $ Process.withProcessGroup processConfig \p ->- case testTimeout of- TestTimeout secs | secs <= 0 -> Right <$> runToCompletion p- TestTimeout secs ->- let duration = fromIntegral secs :: Second- in maybeToRight secs <$> timeout duration (runToCompletion p)+ case Time.convertUnit @_ @Second session.options.timeout of+ secs | secs <= 0 -> Right <$> runToCompletion p+ secs -> maybeToRight secs <$> Timeout.with secs (runToCompletion p) output <- T.unlines . toList <$> readTVarIO outputVar let completed passed = Test.SuiteCompleted@@ -191,19 +189,17 @@ ) => Session -> (Ghci.Loading -> Eff es ())- -> TestTimeout -> RenderedCommand 'Test -> Eff es Test.Suite-runTestSuiteWithGHCi session onProgress testTimeout cmd = do+runTestSuiteWithGHCi session onProgress cmd = do ProjectRoot projectRoot <- ask result <- trySync $ Ghci.Process.with session.ghci cmd projectRoot onProgress noReady \ghci _ ->- case testTimeout of- TestTimeout secs | secs <= 0 -> Right <$> Ghci.Process.exec ghci ":main" noProgress- TestTimeout secs ->- let duration = fromIntegral secs :: Second- in maybeToRight secs- <$> timeout duration (Ghci.Process.exec ghci ":main" noProgress)+ case Time.convertUnit @_ @Second session.options.timeout of+ secs | secs <= 0 -> Right <$> Ghci.Process.exec ghci ":main" noProgress+ secs ->+ maybeToRight secs+ <$> Timeout.with secs (Ghci.Process.exec ghci ":main" noProgress) case result of Left ex -> case fromException ex of Just (e :: Ghci.Process.Error) -> case e of@@ -261,7 +257,7 @@ runScripted results = reinterpret_ (evalState results)- (\(RunTestSuite _ _ _ _) -> popResult)+ (\(RunTestSuite _ _ _) -> popResult) where popResult :: Eff (State [Either SomeException Test.Suite] : es) Test.Suite popResult =
src/Tricorder/Daemon/Watch.hs view
@@ -8,21 +8,16 @@ where import Atelier.Effects.Debounce (Debounce)-import Atelier.Effects.FileWatcher- ( FileEvent- , FileWatcher- , Watch- , containing- , dirExt- , dirWhere- , excluding- )+import Atelier.Effects.FileWatcher (FileWatcher) import Atelier.Effects.PubSub.Pub (Pub) import System.FilePath (takeExtension, takeFileName) import Text.Regex.TDFA (ExecOption (..), blankCompOpt, blankExecOpt, match) import Text.Regex.TDFA.TDFA (patternToRegex) import Atelier.Effects.FileWatcher qualified as FileWatcher+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event)+import Atelier.Effects.FileWatcher.Watch qualified as FileWatcher (Watch)+import Atelier.Effects.FileWatcher.Watch qualified as Watch import Atelier.Effects.PubSub.Pub qualified as Pub import Tricorder.Build.Changes@@ -38,7 +33,7 @@ data WatchedFile = WatchedFile { path :: FilePath- , event :: FileEvent+ , event :: FileWatcher.Event } @@ -72,18 +67,18 @@ | otherwise = SourceChange -specs :: ProjectRoot -> WatchExclusionPatterns -> WatchDirs -> [Watch]+specs :: ProjectRoot -> WatchExclusionPatterns -> WatchDirs -> [FileWatcher.Watch] specs projectRoot watchExclusionPatterns watchDirs = sourceWatches (coerce watchExclusionPatterns) (coerce watchDirs) <> cabalWatches projectRoot -sourceWatches :: [Pattern] -> [FilePath] -> [Watch]+sourceWatches :: [Pattern] -> [FilePath] -> [FileWatcher.Watch] sourceWatches exclusionPatterns = map \d ->- dirExt d ".hs"- `excluding` containing "dist-newstyle"- `excluding` exclusionMatches exclusionPatterns+ Watch.dirExt d ".hs"+ `Watch.excluding` Watch.containing "dist-newstyle"+ `Watch.excluding` exclusionMatches exclusionPatterns exclusionMatches :: [Pattern] -> FilePath -> Bool@@ -95,9 +90,11 @@ fp -cabalWatches :: ProjectRoot -> [Watch]+cabalWatches :: ProjectRoot -> [FileWatcher.Watch] cabalWatches (ProjectRoot projectRoot) =- [dirWhere projectRoot isCabalFile `excluding` containing "dist-newstyle"]+ [ Watch.dirWhere projectRoot isCabalFile+ `Watch.excluding` Watch.containing "dist-newstyle"+ ] isCabalFile :: FilePath -> Bool
src/Tricorder/Logging.hs view
@@ -3,11 +3,7 @@ import Atelier.Effects.Env (Env) import Atelier.Effects.File (BufferMode (..), File) import Atelier.Effects.Input (Input)-import Atelier.Effects.Log- ( Log- , Severity (..)- , runLogToHandle- )+import Atelier.Effects.Log (Log, Severity (..)) import Effectful (IOE) import Effectful.Reader.Static (Reader, asks) @@ -31,10 +27,10 @@ => Eff (Log : es) a -> Eff es a runLogging act = do path <- asks @LogPath (.getLogPath)- File.withFile path AppendMode \h -> do+ File.with path AppendMode \h -> do File.hSetBuffering h LineBuffering (maybeErr, sev) <- getMinimumSeverity- runLogToHandle h sev do+ Log.runToHandle h sev do whenJust maybeErr \err -> Log.err err act
src/Tricorder/Session.hs view
@@ -17,7 +17,6 @@ import Data.Text qualified as T import Effectful.Reader.Static qualified as Reader -import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session.CabalFile (CabalFile) import Tricorder.Session.CommandConfig (CommandConfig (..))@@ -31,7 +30,6 @@ import Tricorder.Session.Stage.Test.Config (TestConfig (..)) import Tricorder.Session.Target (definesCustomPrelude) import Tricorder.Session.TestTarget (getTestTarget)-import Tricorder.Session.TestTimeout (TestTimeout (..)) import Tricorder.Session.Util (displayList, indent) import Tricorder.Session.WatchDirs (WatchDirs (..)) import Tricorder.Session.WatchExclusionPatterns@@ -39,7 +37,6 @@ , resolveWatchExclusionPatterns ) -import Tricorder.Build.ByteSize qualified as ByteSize import Tricorder.Config qualified as Tricorder import Tricorder.Session.Hooks qualified as Hooks import Tricorder.Session.Stage.Build.Session qualified as Build (Session)@@ -55,11 +52,9 @@ { buildSession :: Build.Session , testSession :: Test.Session , evalSession :: Eval.Session- , testMemoryLimit :: Maybe ByteSize , watchDirs :: WatchDirs , watchExclusionPatterns :: WatchExclusionPatterns , replBuildDir :: ReplBuildDir- , testTimeout :: TestTimeout , generateWithHpack :: GenerateWithHpack , hooks :: Hooks , idleTimeout :: IdleTimeout@@ -73,11 +68,9 @@ { buildSession = def , testSession = def , evalSession = def- , testMemoryLimit = Nothing , watchDirs = def , watchExclusionPatterns = def , replBuildDir = def- , testTimeout = def , generateWithHpack = def , hooks = def , idleTimeout = def@@ -111,15 +104,6 @@ warnDeprecatedConfig cfgFile Log.debug "Warned about deprecated config stuff" - testMemoryLimit <- case cfgFile.testMemoryLimit of- Nothing -> pure Nothing- Just limit -> case ByteSize.fromText limit of- Nothing -> do- Log.err $ "Unable to parse test_memory_limit: " <> limit- pure Nothing- Just parsedLimit ->- pure $ Just parsedLimit- watchExclusionPatterns <- case resolveWatchExclusionPatterns cfgFile.watchExclusionPatterns of Left err -> do@@ -135,8 +119,8 @@ repl <- resolveRepl projectRoot buildSession <- Build.Session.resolve cfgFile repl projectFiles- let testSession = Test.Session.resolve repl buildSession.targets cfgFile- evalSession = Eval.Session.resolve repl cfgFile+ testSession <- Test.Session.resolve repl buildSession.targets cfgFile+ let evalSession = Eval.Session.resolve repl cfgFile effectiveTargets = buildSession.targets <> (getTestTarget <$> testSession.targets) watchDirs = WatchDirs.resolve projectRoot projectFiles cfgFile effectiveTargets @@ -151,10 +135,7 @@ warnMissingTargetPlaceholder "test" cfgFile.test.commandConfig.commandTemplate warnMissingTargetPlaceholder "eval" cfgFile.eval.commandTemplate - warnIgnoredextraAutoArguments- "build"- (cfgFile.build.commandTemplate <|> cfgFile.command)- cfgFile.build.extraAutoArguments+ warnIgnoredextraAutoArguments "build" cfgFile.build.commandTemplate cfgFile.build.extraAutoArguments warnIgnoredextraAutoArguments "test" cfgFile.test.commandConfig.commandTemplate@@ -168,9 +149,7 @@ , evalSession , watchDirs , watchExclusionPatterns- , testMemoryLimit , replBuildDir = ReplBuildDir cfgFile.replBuildDir- , testTimeout = TestTimeout cfgFile.testTimeout , generateWithHpack = GenerateWithHpack cfgFile.generateWithHpack , hooks = fromMaybe def cfgFile.hooks , idleTimeout = IdleTimeout $ fromIntegral cfgFile.idleTimeoutSeconds@@ -245,9 +224,6 @@ (toText . showPattern . fst) session.watchExclusionPatterns.getWatchExclusionPatterns , "Repl build dir: " <> toText session.replBuildDir.getReplBuildDir- , -- TODO: Remove " seconds" when TestTimeout is converted to a proper time unit.- "Test timeout: " <> show session.testTimeout.getTestTimeout <> " seconds"- , "Test memory limit: " <> show session.testMemoryLimit , "Generate with hpack: " <> show session.generateWithHpack.getGenerateWithHpack , "Hooks:" , indent $ Hooks.display session.hooks
src/Tricorder/Session/CabalFile.hs view
@@ -128,7 +128,7 @@ => Eff es (Either Text [FilePath]) discoverCabalPackages = do ProjectRoot projectRoot <- Reader.ask- homeCabalFiles <- maybe [] (one . (</> ".cabal/config")) <$> Env.lookupEnv "HOME"+ homeCabalFiles <- maybe [] (one . (</> ".cabal/config")) <$> Env.lookup "HOME" let projectFilePaths = projectCabalFiles projectRoot <> homeCabalFiles projectFiles <- filterM doesFileExist projectFilePaths if null projectFiles
src/Tricorder/Session/Config.hs view
@@ -1,7 +1,8 @@ module Tricorder.Session.Config (Config (..)) where import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults (WithDefaults (..))+import Atelier.Types.JSON.Renamed (Renamed)+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON (..), ToJSON) import Data.Default (Default (..)) @@ -19,20 +20,14 @@ , watchDirs :: [FilePath] , watchExclusionPatterns :: [Text] , replBuildDir :: FilePath- , testTimeout :: Int+ , testTimeout :: Maybe (Renamed "test_timeout" "test.timeout_ms")+ , testMemoryLimit :: Maybe (Renamed "test_memory_limit" "test.memory_limit") , generateWithHpack :: Bool- , testMemoryLimit :: Maybe Text , hooks :: Maybe Hooks , idleTimeoutSeconds :: Int- , command :: Maybe Text- -- ^ DEPRECATED: use 'build'.'commandTemplate' instead.- -- TODO: Remove at or after version 0.8.0.0.- , targets :: [Text]- -- ^ DEPRECATED: use 'build'.'targets' instead.- -- TODO: Remove at or after version 0.8.0.0.- , testTargets :: Maybe [Text]- -- ^ DEPRECATED: use 'test'.'targets' instead.- -- TODO: Remove at or after version 0.8.0.0.+ , command :: Maybe (Renamed "command" "build.command_template")+ , targets :: Maybe (Renamed "targets" "build.targets")+ , testTargets :: Maybe (Renamed "test_targets" "test.targets") } deriving stock (Eq, Generic, Show) deriving (ToJSON) via QuietSnake Config@@ -48,12 +43,12 @@ , watchDirs = [] , watchExclusionPatterns = [] , replBuildDir = "dist-newstyle/tricorder"- , testTimeout = 10- , generateWithHpack = True+ , testTimeout = Nothing , testMemoryLimit = Nothing+ , generateWithHpack = True , hooks = Nothing , idleTimeoutSeconds = 300 , command = Nothing- , targets = []+ , targets = Nothing , testTargets = Nothing }
src/Tricorder/Session/Hooks.hs view
@@ -9,7 +9,7 @@ where import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults (WithDefaults (..))+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON, ToJSON) import Data.Default (Default (..))
src/Tricorder/Session/Stage/Build/Command.hs view
@@ -34,22 +34,20 @@ -> [Target] -> Eff es (CommandTemplate 'Build, [Target]) resolve projectRoot cfg repl effectiveTargets = do- template <- case customTemplate of+ template <- case cfg.build.commandTemplate of Just tpl -> pure tpl Nothing -> defaultBuildTemplate projectRoot repl cfg.replBuildDir pure ( CommandTemplate { repl , template- , arguments = maybe cfg.build.extraAutoArguments (const []) customTemplate+ , arguments = maybe cfg.build.extraAutoArguments (const []) cfg.build.commandTemplate , placeholder = targetsPlaceholder } , if not (null effectiveTargets) then effectiveTargets else [Bare "all"] )- where- customTemplate = cfg.build.commandTemplate <|> cfg.command -- | Whether the project has a @cabal.project@ or any @*.cabal@ file. Used
src/Tricorder/Session/Stage/Build/Session.hs view
@@ -57,7 +57,7 @@ , ghci = maybe def Ghci.Config.File.toConfig config.build.ghci } where- rawBuildTargets = fromMaybe config.targets config.build.targets+ rawBuildTargets = fromMaybe [] config.build.targets effectiveTargets = Target.resolve projectFiles rawBuildTargets
src/Tricorder/Session/Stage/Test/Command.hs view
@@ -4,7 +4,6 @@ ) where -import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Session.Command.RenderedCommand (RenderedCommand (..)) import Tricorder.Session.CommandTemplate (CommandTemplate (..)) import Tricorder.Session.Repl (Repl (..))@@ -22,8 +21,8 @@ } -render :: Session -> Maybe ByteSize -> TestTarget -> RenderedTestCommand-render testSession mMemoryLimit target =+render :: Session -> TestTarget -> RenderedTestCommand+render testSession target = RenderedTestCommand { command = RenderedCommand@@ -57,4 +56,4 @@ Cabal -> cabal Unknown -> cabal )- mMemoryLimit+ testSession.options.memoryLimit
src/Tricorder/Session/Stage/Test/Config.hs view
@@ -5,6 +5,7 @@ ) where +import Atelier.Time (AsRawUnit, Millisecond) import Atelier.Types.JSON.QuietSnake (QuietSnake (..)) import Data.Aeson (FromJSON (..), ToJSON, Value (..)) import Data.Aeson.Types (ToJSON (..))@@ -41,8 +42,10 @@ parseJSON v = TestConfig <$> parseJSON v <*> parseJSON v -newtype Options = Options+data Options = Options { outputMode :: Maybe OutputMode+ , timeoutMs :: Maybe (AsRawUnit Millisecond)+ , memoryLimit :: Maybe Text } deriving stock (Eq, Generic, Show) deriving (FromJSON, ToJSON) via QuietSnake Options@@ -52,6 +55,8 @@ def = Options { outputMode = Nothing+ , timeoutMs = Nothing+ , memoryLimit = Nothing }
src/Tricorder/Session/Stage/Test/Session.hs view
@@ -7,10 +7,15 @@ ) where +import Atelier.Effects.Log (Log)+import Atelier.Time (Millisecond) import Data.Default (Default (..)) +import Atelier.Effects.Log qualified as Log+import Atelier.Time qualified as Time import Data.Text qualified as T +import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Session.CommandConfig (CommandConfig (..)) import Tricorder.Session.CommandTemplate (CommandTemplate (..), targetPlaceholder) import Tricorder.Session.Config (Config (..))@@ -20,6 +25,7 @@ import Tricorder.Session.TestTarget (TestTarget (..)) import Tricorder.Session.Util (displayList, indent) +import Tricorder.Build.ByteSize qualified as ByteSize import Tricorder.Daemon.Ghci.Config qualified as Ghci (Config) import Tricorder.Daemon.Ghci.Config qualified as Ghci.Config import Tricorder.Daemon.Ghci.Config.File qualified as Ghci.Config.File@@ -49,35 +55,54 @@ data ResolvedTestOptions = ResolvedTestOptions { outputMode :: OutputMode+ , timeout :: Millisecond+ , memoryLimit :: Maybe ByteSize } deriving stock (Eq) instance Default ResolvedTestOptions where- def = ResolvedTestOptions ReplOutput+ def =+ ResolvedTestOptions+ { outputMode = ReplOutput+ , timeout = 10+ , memoryLimit = Nothing+ } -resolve :: Repl -> [Target] -> Config -> Session-resolve repl buildTargets cfg =- Session- { commandTemplate =- CommandTemplate- { repl- , template- , arguments =- maybe- cfg.test.commandConfig.extraAutoArguments- (const [])- cfg.test.commandConfig.commandTemplate- , placeholder = targetPlaceholder- }- , targets = TestTarget.resolve cfg buildTargets- , options =- ResolvedTestOptions- { outputMode = fromMaybe detectedOutputMode cfg.test.options.outputMode- }- , ghci = maybe def Ghci.Config.File.toConfig cfg.test.commandConfig.ghci- }+resolve :: (Log :> es) => Repl -> [Target] -> Config -> Eff es Session+resolve repl buildTargets cfg = Log.withNamespace "session.test" do+ memoryLimit <- case cfg.test.options.memoryLimit of+ Nothing -> pure Nothing+ Just limit -> case ByteSize.fromText limit of+ Nothing -> do+ Log.warn $ "Unable to parse options.memory_limit: " <> limit+ Log.info "Continuing with no memory limit."+ pure Nothing+ Just parsedLimit ->+ pure $ Just parsedLimit+ pure+ $ Session+ { commandTemplate =+ CommandTemplate+ { repl+ , template+ , arguments =+ maybe+ cfg.test.commandConfig.extraAutoArguments+ (const [])+ cfg.test.commandConfig.commandTemplate+ , placeholder = targetPlaceholder+ }+ , targets = TestTarget.resolve cfg buildTargets+ , options =+ ResolvedTestOptions+ { outputMode = fromMaybe detectedOutputMode cfg.test.options.outputMode+ , timeout = maybe 10 Time.getAsRawUnit cfg.test.options.timeoutMs+ , memoryLimit+ }+ , ghci = maybe def Ghci.Config.File.toConfig cfg.test.commandConfig.ghci+ } where template = fromMaybe (defaultTestTemplate repl) cfg.test.commandConfig.commandTemplate detectedOutputMode@@ -114,4 +139,6 @@ T.intercalate "\n" [ "Output mode: " <> show opts.outputMode+ , "Timeout: " <> show opts.timeout+ , "Memory limit: " <> maybe "<unlimited>" show opts.memoryLimit ]
src/Tricorder/Session/State.hs view
@@ -27,6 +27,7 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileWatcher qualified as FileWatcher+import Atelier.Effects.FileWatcher.Watch qualified as Watch import Atelier.Effects.Input qualified as Input import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub qualified as PubSub@@ -136,7 +137,7 @@ watchConfigFile = do root <- Reader.ask FileWatcher.watchFilePathsDebounced- [FileWatcher.dirWhere root.getProjectRoot (LoadedConfig.configFileName `List.isSuffixOf`)]+ [Watch.dirWhere root.getProjectRoot (LoadedConfig.configFileName `List.isSuffixOf`)] \_ _ -> Pub.publish Reload @@ -154,7 +155,7 @@ exists <- FileSystem.doesFileExist $ root.getProjectRoot </> "stack.yaml" when exists do Conc.fork_ $ FileWatcher.watchFilePathsDebounced- [FileWatcher.dirWhere root.getProjectRoot ("stack.yaml" `List.isSuffixOf`)]+ [Watch.dirWhere root.getProjectRoot ("stack.yaml" `List.isSuffixOf`)] \_ _ -> Pub.publish Reload
src/Tricorder/Session/TestTarget.hs view
@@ -50,6 +50,6 @@ -- ever names test suites [ref:test_targets_invariant]. With neither source -- set, falls back to deriving test targets from the build 'targets'. resolve :: Config -> [Target] -> [TestTarget]-resolve cfg targets = case cfg.test.commandConfig.targets <|> cfg.testTargets of+resolve cfg targets = case cfg.test.commandConfig.targets of Just explicit -> mapMaybe parse explicit Nothing -> mapMaybe project targets
− src/Tricorder/Session/TestTimeout.hs
@@ -1,13 +0,0 @@-module Tricorder.Session.TestTimeout (TestTimeout (..)) where--import Data.Aeson (FromJSON, ToJSON)-import Data.Default (Default (..))---newtype TestTimeout = TestTimeout {getTestTimeout :: Int}- deriving stock (Eq, Generic, Show)- deriving (FromJSON, ToJSON) via Int---instance Default TestTimeout where- def = TestTimeout 10
src/Tricorder/Socket/Client.hs view
@@ -20,14 +20,14 @@ import Effectful.Reader.Static (Reader, ask) import Effectful.State.Static.Shared (evalState, get, modify, put) import System.IO.Error (isEOFError)-import Tricorder.API.Force (Force)+import Tricorder.API.IfThereArePendingQueries (IfThereArePendingQueries) import Prelude hiding (force) import Atelier.Effects.Delay qualified as Delay import Atelier.Effects.File qualified as File import Atelier.Effects.Posix.Daemons qualified as Daemons import Data.ByteString.Lazy qualified as BSL-import Tricorder.API.Force qualified as Force+import Tricorder.API.IfThereArePendingQueries qualified as IfThereArePendingQueries import Tricorder.Build (BuildState, Diagnostic) import Tricorder.Runtime (PidFile)@@ -126,7 +126,7 @@ requestShutdown :: (File :> es, UnixSocket :> es)- => Force -> FilePath -> Eff es (Either Text ())+ => IfThereArePendingQueries -> FilePath -> Eff es (Either Text ()) requestShutdown force sockPath = withConnection sockPath \h -> do sendQuery h $ Quit waiters line <- File.hGetLine h@@ -137,8 +137,8 @@ pure $ Left "Failed to request shutdown" where waiters = case force of- Force.Force -> IgnoreWaiters- Force.NoForce -> WaitForWaiters+ IfThereArePendingQueries.DontWait -> IgnoreWaiters+ IfThereArePendingQueries.Wait -> WaitForWaiters isDaemonRunning :: (Daemons :> es, Reader PidFile :> es) => Eff es Bool
src/Tricorder/Socket/Server.hs view
@@ -1,7 +1,7 @@ module Tricorder.Socket.Server (main, SocketRemoved (..)) where import Atelier.Effects.Conc (Conc)-import Atelier.Effects.Exit (Exit, exitSuccess)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.File (File) import Atelier.Effects.FileSystem (FileSystem) import Atelier.Effects.Input (Input, input)@@ -14,6 +14,7 @@ import System.IO (Handle) import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.Exit qualified as Exit import Atelier.Effects.File qualified as File import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.Log qualified as Log@@ -154,7 +155,7 @@ doQuit = do sendJson h True Log.info "Shutting down."- exitSuccess+ Exit.success respondOnce
src/Tricorder/SourceLookup.hs view
@@ -6,7 +6,7 @@ where import Atelier.Effects.FileSystem (FileSystem, doesFileExist, readFileLbs)-import Atelier.Effects.Input (Input, input)+import Atelier.Effects.Input (Input) import Atelier.Effects.Log (Log) import System.FilePath (normalise, takeDirectory, (</>)) import Tricorder.SourceLookup.ModuleSourceResult (ModuleSourceResult (..))@@ -14,13 +14,15 @@ import Tricorder.SourceLookup.ReexportLimits (ReexportLimits (..)) import Tricorder.SourceLookup.SourceQuery (ModuleName (..), SourceQuery (..)) +import Atelier.Effects.Input qualified as Input import Atelier.Effects.Log qualified as Log import Data.Set qualified as Set import Data.Text qualified as T import Tricorder.SourceLookup.ModuleSourceResult qualified as ModuleSourceResult import Tricorder.Session.CabalFile (CabalFile (..))-import Tricorder.Session.Repl (Repl)+import Tricorder.Session.CommandTemplate (CommandTemplate (..))+import Tricorder.Session.Stage.Build.Session (Session (..)) import Tricorder.Session.WatchDirs (allSourceDirs) import Tricorder.SourceLookup.GhcPkg (GhcPkg) import Tricorder.SourceLookup.Hackage (Hackage)@@ -34,6 +36,7 @@ , sourceExtensions ) +import Tricorder.Session.Stage.Build.Session qualified as Build (Session) import Tricorder.SourceLookup.GhcPkg qualified as GhcPkg @@ -83,7 +86,7 @@ :: ( FileSystem :> es , GhcPkg :> es , Hackage :> es- , Input Repl :> es+ , Input Build.Session :> es , Log :> es , PackageStore :> es )@@ -111,7 +114,7 @@ :: ( FileSystem :> es , GhcPkg :> es , Hackage :> es- , Input Repl :> es+ , Input Build.Session :> es , Log :> es , PackageStore :> es )@@ -180,7 +183,7 @@ :: ( FileSystem :> es , GhcPkg :> es , Hackage :> es- , Input Repl :> es+ , Input Build.Session :> es , Log :> es , PackageStore :> es )@@ -238,14 +241,14 @@ -- | Resolve a module to its package, consulting the module -> package cache first. resolvePackage :: ( GhcPkg :> es- , Input Repl :> es+ , Input Build.Session :> es , Log :> es ) => ModuleName -> Eff es (Maybe PackageId) resolvePackage modName = do- repl <- input- result <- GhcPkg.findModule repl modName+ session <- Input.input @Build.Session+ result <- GhcPkg.findModule session.commandTemplate.repl modName Log.debug $ "Source: find-module " <> unModuleName modName <> " → " <> show result pure result
src/Tricorder/SourceLookup/PackageStore.hs view
@@ -6,7 +6,7 @@ ) where -import Atelier.Effects.Env (Env, getEnvironment)+import Atelier.Effects.Env (Env) import Atelier.Effects.FileSystem (FileSystem) import Effectful (Effect) import Effectful.Dispatch.Dynamic (interpretWith_)@@ -15,6 +15,7 @@ import System.FilePath (takeDirectory, (</>)) import Tricorder.SourceLookup.PackageId (PackageId (..), splitPackageId) +import Atelier.Effects.Env qualified as Env import Atelier.Effects.FileSystem qualified as FileSystem import Data.Map.Strict qualified as Map @@ -66,7 +67,7 @@ findStoreBaseDir :: (Env :> es, FileSystem :> es) => Eff es FilePath findStoreBaseDir = do- env <- Map.fromList <$> getEnvironment+ env <- Map.fromList <$> Env.all fmap (fromMaybe tempFallback . rightToMaybe) $ runNonDet OnEmptyKeep $ findCabalDirCandidate env
test/Unit/Tricorder/Daemon/Ghci/ProcessSpec.hs view
@@ -1,12 +1,8 @@ module Unit.Tricorder.Daemon.Ghci.ProcessSpec (test_Process) where -import Atelier.Effects.Conc (runConc)-import Atelier.Effects.Delay (runDelay)-import Atelier.Effects.File (runFile)-import Atelier.Effects.Log (Message (..), Severity (..), runLogNoOp, runLogWriter)-import Atelier.Effects.Process (runProcessIO, terminateProcessGroup, withProcessGroup)+import Atelier.Effects.Log (Message (..), Severity (..))+import Atelier.Effects.Process (terminateProcessGroup, withProcessGroup) import Atelier.Effects.Process.Internal (RunningProcess (..))-import Atelier.Effects.Timeout (runTimeout) import Atelier.Time (Millisecond) import Control.Concurrent (threadDelay) import Control.Concurrent.STM (newTVarIO)@@ -40,7 +36,10 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.Delay qualified as Delay import Atelier.Effects.File qualified as File+import Atelier.Effects.Log qualified as Log import Atelier.Effects.Process qualified as AProc+import Atelier.Effects.Process qualified as Atelier.Process+import Atelier.Effects.Timeout qualified as Timeout import Data.Text qualified as T import System.Process qualified as Process import System.Timeout qualified@@ -81,8 +80,8 @@ (result, _msgs) <- runEff . runWriter @[Message]- . runLogWriter- . runFile+ . Log.runWriter+ . File.run $ do for_ ["line1", "line2", finishMarker 1, "line3"] (File.hPutTextLn w) File.hClose w@@ -94,8 +93,8 @@ (result, _msgs) <- runEff . runWriter @[Message]- . runLogWriter- . runFile+ . Log.runWriter+ . File.run $ do for_ ["a", "b", "c", finishMarker 2] (File.hPutTextLn w) File.hClose w@@ -108,8 +107,8 @@ (result, _msgs) <- runEff . runWriter @[Message]- . runLogWriter- . runFile+ . Log.runWriter+ . File.run $ do -- 'finishMarker 5' is a leftover from a prior, interrupted -- command; the drain waiting for 'finishMarker 9' must@@ -125,8 +124,8 @@ (outcome, _msgs) <- runEff . runWriter @[Message]- . runLogWriter- . runFile+ . Log.runWriter+ . File.run $ do for_ ["first", "second", "third"] (File.hPutTextLn w) File.hClose w -- EOF before the marker ever arrives@@ -144,8 +143,8 @@ (outcome, _msgs) <- runEff . runWriter @[Message]- . runLogWriter- . runFile+ . Log.runWriter+ . File.run $ do File.hClose w trySync (Process.drainUntil r (finishMarker 1) (\_ -> pure ()))@@ -170,8 +169,8 @@ (_outcome, msgs) <- runEff . runWriter @[Message]- . runLogWriter- . runFile+ . Log.runWriter+ . File.run $ do File.hClose w trySync (Process.drainUntil r (finishMarker 3) (\_ -> pure ()))@@ -185,8 +184,8 @@ (_outcome, msgs) <- runEff . runWriter @[Message]- . runLogWriter- . runFile+ . Log.runWriter+ . File.run $ do for_ ["oops-line-1", "oops-line-2"] (File.hPutTextLn w) File.hClose w@@ -235,11 +234,11 @@ result <- runEff . runConcurrent- . runTimeout- . runDelay- . runFile- . runLogNoOp- . runConc+ . Timeout.run+ . Delay.runIO+ . File.run+ . Log.runNoOp+ . Conc.runIO $ do -- A stale 'marker 5' (left by a prior interrupted reload) -- precedes the fresh command's real output + its 'marker 9'@@ -294,11 +293,11 @@ written <- runEff . runConcurrent- . runTimeout- . runDelay- . runFile- . runLogNoOp- . runConc+ . Timeout.run+ . Delay.runIO+ . File.run+ . Log.runNoOp+ . Conc.runIO $ do -- Pre-seed the marker on both streams so the drain returns -- immediately; we only care about what was written to stdin.@@ -339,10 +338,10 @@ result <- runEff . runConcurrent- . runTimeout- . runDelay- . runFile- . runConc+ . Timeout.run+ . Delay.runIO+ . File.run+ . Conc.runIO $ do -- No banner will ever arrive: close the write end so the -- wait sees EOF at once and takes the "command exited"@@ -383,11 +382,11 @@ let scenario = runEff . runConcurrent- . runTimeout- . runDelay- . runFile- . runConc- . runProcessIO+ . Timeout.run+ . Delay.runIO+ . File.run+ . Conc.runIO+ . Atelier.Process.run $ withProcessGroup procConfig \p -> do -- First stdout line is the long-lived child's pid. line <- File.hGetLine (AProc.getStdout p)@@ -439,7 +438,7 @@ ignoring (stopProcess p) assertFailure ("could not parse child pid from: " <> show childLine) Just childPid -> do- runEff . runProcessIO $ terminateProcessGroup (RunningProcess p)+ runEff . Atelier.Process.run $ terminateProcessGroup (RunningProcess p) died <- waitForProcessDeath childPid -- Never leak the child if the assertion fails. ignoring (signalProcess sigKILL (fromIntegral childPid))@@ -532,11 +531,11 @@ result <- runEff . runConcurrent- . runTimeout- . runDelay- . runFile- . runLogNoOp- . runConc+ . Timeout.run+ . Delay.runIO+ . File.run+ . Log.runNoOp+ . Conc.runIO $ Conc.scoped do -- A sibling fork in the SAME ambient scope. If the bug -- regresses, the drain exception will tear this scope
test/Unit/Tricorder/Daemon/Ghci/SessionSpec.hs view
@@ -149,4 +149,4 @@ :: [Either SomeException LoadResult] -> Eff '[Session, Pub BuildProgress, IOE] a -> IO a-runScripted results = runEff . Pub.runNoOp . Session.runScripted results+runScripted results = runEff . Pub.noOp . Session.runScripted results
test/Unit/Tricorder/Daemon/TestRunnerSpec.hs view
@@ -16,7 +16,6 @@ ) import Tricorder.Session.Command.RenderedCommand (RenderedCommand (..)) import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..))-import Tricorder.Session.TestTimeout (TestTimeout (..)) import Tricorder.Build.Test qualified as Test import Tricorder.Daemon.TestRunner qualified as TestRunner@@ -107,7 +106,7 @@ [ testCase "returns scripted TestRun" do result <- runScripted [Right passingRun]- $ runTestSuite def noProgress testTimeout+ $ runTestSuite def noProgress $ mkResolved $ RenderedCommand $ "cabal repl test:foo"@@ -115,7 +114,7 @@ , testCase "ignores the target name argument" do result <- runScripted [Right failingRun]- $ runTestSuite def noProgress testTimeout+ $ runTestSuite def noProgress $ mkResolved $ RenderedCommand $ "cabal repl test:anything"@@ -124,7 +123,7 @@ result <- runScripted [Left (toException boom)] $ try @ErrorCall- $ runTestSuite def noProgress testTimeout+ $ runTestSuite def noProgress $ mkResolved $ RenderedCommand $ "cabal repl test:foo"@@ -134,12 +133,12 @@ [ testCase "consumes results in order across multiple calls" do (a, b) <- runScripted [Right passingRun, Right failingRun] do a <-- runTestSuite def noProgress testTimeout+ runTestSuite def noProgress $ mkResolved $ RenderedCommand $ "cabal repl test:foo" b <-- runTestSuite def noProgress testTimeout+ runTestSuite def noProgress $ mkResolved $ RenderedCommand $ "cabal repl test:bar"@@ -150,12 +149,12 @@ result <- runScripted [Left (toException boom), Right passingRun] do r1 <- try @ErrorCall- $ runTestSuite def noProgress testTimeout+ $ runTestSuite def noProgress $ mkResolved $ RenderedCommand $ "cabal repl test:foo" r2 <-- runTestSuite def noProgress testTimeout+ runTestSuite def noProgress $ mkResolved $ RenderedCommand $ "cabal repl test:bar"@@ -202,10 +201,6 @@ mkResolved :: RenderedCommand 'Stage.Test -> RenderedTestCommand mkResolved cmd = RenderedTestCommand cmd def---testTimeout :: TestTimeout-testTimeout = TestTimeout (-1) noProgress :: b -> Eff es ()
test/Unit/Tricorder/Daemon/WatchSpec.hs view
@@ -1,6 +1,7 @@ module Unit.Tricorder.Daemon.WatchSpec (test_Watch) where -import Atelier.Effects.FileWatcher (FileEvent (..), matchesAny)+import Atelier.Effects.FileWatcher (matchesAny)+import Atelier.Effects.FileWatcher.Event (Event (..)) import Effectful (runEff) import Effectful.Writer.Static.Shared (execWriter, runWriter) import Test.Tasty (TestTree, testGroup)@@ -9,10 +10,7 @@ import Atelier.Effects.PubSub.Pub qualified as Pub -import Tricorder.Build.Changes- ( CabalChangeDetected (..)- , SourceChangeDetected (..)- )+import Tricorder.Build.Changes (CabalChangeDetected (..), SourceChangeDetected (..)) import Tricorder.Daemon.Watch (WatchedFile (..)) import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session.WatchDirs (WatchDirs (..))
test/Unit/Tricorder/Session/CabalFileSpec.hs view
@@ -1,16 +1,16 @@ module Unit.Tricorder.Session.CabalFileSpec (test_CabalFile) where -import Atelier.Effects.Env (runEnvConst)-import Atelier.Effects.FileSystem (runFileSystemState)-import Atelier.Effects.Log (runLogNoOp) import Effectful (runPureEff) import Effectful.Reader.Static (runReader) import Effectful.State.Static.Shared (evalState) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.Env qualified as Env+import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileSystem.Glob qualified as Glob import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log import Data.Map.Strict qualified as Map import Tricorder.Runtime (ProjectRoot (..))@@ -156,9 +156,9 @@ runDiscovery fs env = runDiscoveryGlob fs env [] runDiscoveryGlob fs env script = runPureEff- . runEnvConst env+ . Env.runConst env . evalState fs- . runFileSystemState+ . FileSystem.runState . Glob.runScripted script . runReader pr @@ -182,7 +182,7 @@ runStack result = runPureEff . runReader pr- . runLogNoOp+ . Log.runNoOp . Input.withConst result @@ -206,4 +206,4 @@ fmap (.projectFilePath) . runPureEff . evalState fs- . runFileSystemState+ . FileSystem.runState
test/Unit/Tricorder/Session/ReplSpec.hs view
@@ -1,11 +1,12 @@ module Unit.Tricorder.Session.ReplSpec (test_Repl) where -import Atelier.Effects.FileSystem (FileSystem, runFileSystemState)+import Atelier.Effects.FileSystem (FileSystem) import Effectful (runPureEff) import Effectful.State.Static.Shared (State, evalState) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.FileSystem qualified as FileSystem import Data.Map.Strict qualified as Map import Tricorder.Runtime (ProjectRoot (..))@@ -45,7 +46,7 @@ -- @stack.yaml@, which is parsed for its @packages@ key). withFiles :: [(FilePath, ByteString)] -> Eff '[FileSystem, State (Map FilePath ByteString)] a -> a withFiles files action =- runPureEff $ evalState (Map.fromList files) $ runFileSystemState action+ runPureEff $ evalState (Map.fromList files) $ FileSystem.runState action pr :: ProjectRoot
test/Unit/Tricorder/Session/Stage/Build/CommandSpec.hs view
@@ -1,12 +1,13 @@ module Unit.Tricorder.Session.Stage.Build.CommandSpec (test_Command) where -import Atelier.Effects.FileSystem (FileSystem, runFileSystemState)+import Atelier.Effects.FileSystem (FileSystem) import Data.Default (def) import Effectful (runPureEff) import Effectful.State.Static.Shared (State, evalState) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.FileSystem qualified as FileSystem import Data.Map.Strict qualified as Map import Tricorder.Runtime (ProjectRoot (..))@@ -74,22 +75,6 @@ testResolveBuildCommand :: [TestTree] testResolveBuildCommand = [ testGroup- "deprecated top-level command"- [ testCase "is used as the build template when build.command_template is unset" do- renderBuildFor [] def {command = Just "foo"} [] @?= "foo"- ]- , testGroup- "build.command_template"- [ testCase "overrides the deprecated top-level command" do- let cfg =- cfg0- { command = Just "should be ignored"- , build = cfg0.build {commandTemplate = Just "cabal repl {targets}"}- }- renderBuildFor [("/cabal.project", "")] cfg (Target.parse <$> ["lib:foo"])- @?= "cabal repl lib:foo"- ]- , testGroup "explicit targets" [ testCase "spell them out verbatim" do renderBuildFor [("/cabal.project", "")] cfg0 (Target.parse <$> ["lib:foo"])@@ -145,14 +130,6 @@ } renderBuildFor [("/cabal.project", "")] cfg (Target.parse <$> ["lib:foo"]) @?= "cabal repl lib:foo"- , testCase "is ignored when the deprecated top-level command is set" do- let cfg =- cfg0- { command = Just "cabal repl {targets}"- , build = cfg0.build {extraAutoArguments = ["--extra-flag"]}- }- renderBuildFor [("/cabal.project", "")] cfg (Target.parse <$> ["lib:foo"])- @?= "cabal repl lib:foo" ] ] @@ -171,7 +148,7 @@ -- @stack.yaml@, which is parsed for its @packages@ key). withFiles :: [(FilePath, ByteString)] -> Eff '[FileSystem, State (Map FilePath ByteString)] a -> a withFiles files action =- runPureEff $ evalState (Map.fromList files) $ runFileSystemState action+ runPureEff $ evalState (Map.fromList files) $ FileSystem.runState action cfg0 :: Config
test/Unit/Tricorder/Session/Stage/Test/CommandSpec.hs view
@@ -1,25 +1,26 @@ module Unit.Tricorder.Session.Stage.Test.CommandSpec (test_Command) where import Data.Default (def)+import Effectful (runPureEff) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.Log qualified as Log+ import Tricorder.Build.ByteSize (ByteSize (..), Unit (..)) import Tricorder.Session.Command.RenderedCommand (RenderedCommand (..)) import Tricorder.Session.CommandConfig (CommandConfig (..)) import Tricorder.Session.CommandTemplate (CommandTemplate (..), targetPlaceholder) import Tricorder.Session.Config (Config (..)) import Tricorder.Session.Repl (Repl (..))-import Tricorder.Session.Stage.Test.Command- ( RenderedTestCommand (..)- , render- )+import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..), render) import Tricorder.Session.Stage.Test.Config (TestConfig (..))-import Tricorder.Session.Stage.Test.Session (Session (..), resolve)+import Tricorder.Session.Stage.Test.Session (ResolvedTestOptions (..), Session (..)) import Tricorder.Session.TestTarget (TestTarget (..)) import Tricorder.Session.CommandConfig qualified as CommandConfig import Tricorder.Session.Stage qualified as Stage+import Tricorder.Session.Stage.Test.Session qualified as Session import Tricorder.Session.Target qualified as Target @@ -61,7 +62,7 @@ where test :: CommandTemplate 'Stage.Test -> Maybe ByteSize -> TestTarget -> Text test template mMemoryLimit target =- (render (Session template [] def def) mMemoryLimit target).command.getRenderedCommand+ (render (Session template [] (def {memoryLimit = mMemoryLimit}) def) target).command.getRenderedCommand testTarget = TestTarget (Target.parse "test:foo") oneByte = ByteSize 1 B @@ -104,3 +105,7 @@ } (resolve Cabal [] cfg).commandTemplate.arguments @?= [] ]+++resolve :: Repl -> [Target.Target] -> Config -> Session+resolve repl target config = runPureEff . Log.runNoOp $ Session.resolve repl target config
test/Unit/Tricorder/Session/TestTargetSpec.hs view
@@ -6,6 +6,9 @@ import Tricorder.Session.Config (Config (..)) +import Tricorder.Session.CommandConfig qualified as CommandConfig+import Tricorder.Session.Config qualified as Config+import Tricorder.Session.Stage.Test.Config qualified as Test.Config import Tricorder.Session.Target qualified as Target import Tricorder.Session.TestTarget qualified as TestTarget @@ -28,12 +31,30 @@ let cfg = def :: Config TestTarget.resolve cfg (mkTargets ["lib:mylib", "exe:myapp"]) @?= mapMaybe TestTarget.parse []- , testCase "uses explicit testTargets list when set" do- let cfg = def {testTargets = Just ["test:b-test"]} :: Config+ , testCase "uses explicit test.commandConfig.targets list when set" do+ let cfg =+ def+ { Config.test =+ def+ { Test.Config.commandConfig =+ def+ { CommandConfig.targets = Just ["test:b-test"]+ }+ }+ } TestTarget.resolve cfg (mkTargets ["lib:a", "test:a-test", "test:b-test"]) @?= mapMaybe TestTarget.parse ["test:b-test"] , testCase "returns empty list when testTargets is explicitly empty" do- let cfg = def {testTargets = Just []} :: Config+ let cfg =+ def+ { Config.test =+ def+ { Test.Config.commandConfig =+ def+ { CommandConfig.targets = Just []+ }+ }+ } TestTarget.resolve cfg (mkTargets ["lib:a", "test:a-test"]) @?= [] , testCase "infers multiple test: components" do
test/Unit/Tricorder/SessionSpec.hs view
@@ -1,7 +1,6 @@ module Unit.Tricorder.SessionSpec (test_Session) where -import Atelier.Effects.FileSystem (runFileSystemState)-import Atelier.Effects.Log (Message (..), Severity (..), runLogNoOp, runLogWriter)+import Atelier.Effects.Log (Message (..), Severity (..)) import Data.Aeson (object, (.=)) import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe) import Effectful (runPureEff)@@ -11,7 +10,9 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertFailure, testCase, (@?), (@?=)) +import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log import Data.Aeson qualified as Aeson import Data.Text qualified as T @@ -78,9 +79,9 @@ filter (\m -> m.severity /= DEBUG) . runPureEff . execWriter @[Message]- . runLogWriter+ . Log.runWriter . evalState @(Map FilePath ByteString) mempty- . runFileSystemState+ . FileSystem.runState . Input.withConst cabalFiles . runReader (ProjectRoot "/") . Input.withConst (LoadedConfig $ toStrict $ Aeson.encode cfg)@@ -122,9 +123,9 @@ captureLogsFor cfg = runPureEff . execWriter @[Message]- . runLogWriter+ . Log.runWriter . evalState @(Map FilePath ByteString) mempty- . runFileSystemState+ . FileSystem.runState . Input.withConst ([] :: [CabalFile]) . runReader (ProjectRoot "/") . Input.withConst (LoadedConfig $ toStrict $ Aeson.encode cfg)@@ -145,9 +146,9 @@ where loadSessionWith cfg = runPureEff- . runLogNoOp+ . Log.runNoOp . evalState @(Map FilePath ByteString) mempty- . runFileSystemState+ . FileSystem.runState . Input.withConst ([] :: [CabalFile]) . runReader (ProjectRoot "/") . Input.withConst (LoadedConfig $ toStrict $ Aeson.encode cfg)
test/Unit/Tricorder/SocketSpec.hs view
@@ -1,6 +1,6 @@ module Unit.Tricorder.SocketSpec (test_Socket) where -import Atelier.Effects.File (File, runFile)+import Atelier.Effects.File (File) import Atelier.Effects.FileSystem (FileSystem) import Effectful (IOE, runEff) import Effectful.Exception (trySync)@@ -8,6 +8,7 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.File qualified as File import Atelier.Effects.FileSystem qualified as FileSystem import Tricorder.Socket.Client (isDaemonReady)@@ -75,9 +76,9 @@ -- | Run scripted socket operations (no Delay needed). runScripted :: [SocketScript] -> Eff '[UnixSocket, File, IOE] a -> IO a-runScripted script = runEff . runFile . UnixSocket.runScripted script+runScripted script = runEff . File.run . UnixSocket.runScripted script -- | Run socket operations against the real IO interpreter. runIO' :: Eff '[UnixSocket, File, FileSystem, IOE] a -> IO a-runIO' = runEff . FileSystem.runFileSystemIO . runFile . UnixSocket.runIO+runIO' = runEff . FileSystem.runIO . File.run . UnixSocket.runIO
test/Unit/Tricorder/SourceLookupSpec.hs view
@@ -1,9 +1,10 @@ module Unit.Tricorder.SourceLookupSpec (test_SourceLookup) where -import Atelier.Effects.Env (Env, runEnvConst)+import Atelier.Effects.Env (Env) import Atelier.Effects.FileSystem (FileSystem (..)) import Atelier.Effects.Input (Input)-import Atelier.Effects.Log (Log, runLogNoOp)+import Atelier.Effects.Log (Log)+import Data.Default (def) import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe) import Effectful (IOE, runEff) import Effectful.Concurrent (Concurrent, runConcurrent)@@ -16,7 +17,9 @@ import Tricorder.SourceLookup.ReexportLimits (ReexportLimits (..), defaultReexportLimits) import Tricorder.SourceLookup.SourceQuery (ModuleName, SourceQuery (..)) +import Atelier.Effects.Env qualified as Env import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log import Codec.Archive.Tar qualified as Tar import Codec.Archive.Tar.Entry qualified as Tar import Codec.Compression.GZip qualified as GZip@@ -26,7 +29,6 @@ import Data.Text qualified as T import Tricorder.Session.CabalFile (CabalFile (..))-import Tricorder.Session.Repl (Repl (..)) import Tricorder.SourceLookup ( ProjectSourceDirs (..) , lookupModuleSource@@ -36,6 +38,7 @@ import Tricorder.SourceLookup.Hackage (Hackage (..), Result (..)) import Tricorder.SourceLookup.PackageStore (PackageStore) +import Tricorder.Session.Stage.Build.Session qualified as Build import Tricorder.SourceLookup.PackageStore qualified as PackageStore @@ -399,7 +402,7 @@ , Env , FileSystem , State (Map FilePath LByteString)- , Input Repl+ , Input Build.Session , Log , Concurrent , IOE@@ -418,11 +421,11 @@ runTest pkgScript initialFs onFetch action = runEff . runConcurrent- . runLogNoOp- . Input.withConst Cabal+ . Log.runNoOp+ . Input.withConst (def :: Build.Session) . evalState initialFs . runFileSystemFake- . runEnvConst [("HOME", "/h")]+ . Env.runConst [("HOME", "/h")] . PackageStore.run . runHackageWith onFetch . runGhcPkgScripted pkgScript
test/Unit/Tricorder/WaitersSpec.hs view
@@ -1,6 +1,6 @@ module Unit.Tricorder.WaitersSpec (test_Waiters) where -import Atelier.Effects.Conc (Conc, runConc)+import Atelier.Effects.Conc (Conc) import Control.Exception (try) import Effectful (IOE, runEff) import Effectful.Concurrent (Concurrent, runConcurrent)@@ -30,7 +30,7 @@ runWaitersTest = runEff . runConcurrent- . runConc+ . Conc.runIO . runState @[Text] [] . Waiters.run
tricorder.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: tricorder-version: 0.7.0.0+version: 1.0.0.0 synopsis: Continuous Haskell build status, diagnostics, and tests via a shared daemon description: tricorder rebuilds your Haskell project continuously and surfaces build status, diagnostics, test results, and documentation - for developers and LLM coding agents. Like ghcid and ghciwatch it reloads on every change, but builds run in a background daemon so multiple clients (an interactive TUI, a status CLI, an agent skill) share a single build state without triggering redundant rebuilds. It discovers components across multi-package cabal.project workspaces automatically and ships context-friendly output for agentic use via the CLI. category: Development@@ -125,7 +125,6 @@ Tricorder.Session.State.Result Tricorder.Session.Target Tricorder.Session.TestTarget- Tricorder.Session.TestTimeout Tricorder.Session.Util Tricorder.Session.WatchDirs Tricorder.Session.WatchExclusionPatterns@@ -172,7 +171,7 @@ , Cabal-syntax >=3.12 && <3.17 , Glob ==0.10.* , aeson >=2.2 && <2.4- , atelier-core ==0.8.*+ , atelier-core ==1.0.* , atelier-prelude ==0.4.* , base >=4.18 && <4.23 , brick ==3.0.*@@ -201,7 +200,7 @@ , text ==2.1.* , time >=1.12 && <1.17 , time-units ==1.0.*- , tricorder-types ==0.6.*+ , tricorder-types ==1.0.* , vty >=6.5 && <6.7 , vty-crossplatform ==0.5.* , yaml ==0.11.*@@ -354,7 +353,7 @@ build-depends: Cabal-syntax >=3.12 && <3.17 , aeson >=2.2 && <2.4- , atelier-core ==0.8.*+ , atelier-core ==1.0.* , atelier-prelude ==0.4.* , base >=4.18 && <4.23 , bytestring >=0.11 && <0.13@@ -376,7 +375,7 @@ , time >=1.12 && <1.17 , time-units ==1.0.* , tricorder-internal- , tricorder-types ==0.6.*+ , tricorder-types ==1.0.* , typed-process ==0.2.* , unagi-chan ==0.4.* , unix ==2.8.*