packages feed

tricorder-0.6.0.0: src/Tricorder/CLI/App/Test.hs

module Tricorder.CLI.App.Test (run) where

import Atelier.Effects.Console (Console)
import Atelier.Effects.Delay (Delay)
import Atelier.Effects.Exit (Exit, exitFailure)
import Atelier.Effects.File (File)
import Atelier.Effects.Posix.Daemons (Daemons, PidFile)
import Effectful (IOE)
import Effectful.Reader.Static (Reader)
import Tricorder.CLI.Command.IfDaemonIsStopped (IfDaemonIsStopped)
import Tricorder.CLI.Command.OutputFormat (OutputFormat (..))
import Tricorder.CLI.Command.Test (Command (..), SuitesOptions (..))
import Tricorder.CLI.Command.WaitMode (WaitMode (..))

import Atelier.Effects.Console qualified as Console
import Effectful.Reader.Static qualified as Reader
import Tricorder.CLI.Command.IfDaemonIsStopped qualified as IfDaemonIsStopped
import Tricorder.FieldMask.Schema qualified as FieldMask.Schema

import Tricorder.Build (BuildState (..))
import Tricorder.CLI.App.Test.SuiteView (defaultFields, schema)
import Tricorder.Runtime (SocketPath (..))
import Tricorder.Socket.Client (queryStatus, queryStatusWait)
import Tricorder.Socket.UnixSocket (UnixSocket)

import Tricorder.CLI.App.Test.Json qualified as Json
import Tricorder.CLI.App.Test.Text qualified as Text
import Tricorder.CLI.Daemon qualified as Daemons
import Tricorder.Session.TestTarget qualified as TestTarget
import Tricorder.Socket.Client qualified as Client


run
    :: ( Console :> es
       , Daemons :> es
       , Delay :> es
       , Exit :> es
       , File :> es
       , IOE :> es
       , Reader PidFile :> es
       , Reader SocketPath :> es
       , UnixSocket :> es
       )
    => Command -> Eff es ()
run command =
    printResult =<< case command of
        Suites opts -> showSuites opts


printResult :: (Console :> es, Exit :> es) => Either Text Text -> Eff es ()
printResult = \case
    Left err -> do
        Console.putTextLn err
        exitFailure
    Right res ->
        Console.putTextLn res


showSuites
    :: ( Daemons :> es
       , Delay :> es
       , File :> es
       , IOE :> es
       , Reader PidFile :> es
       , Reader SocketPath :> es
       , UnixSocket :> es
       )
    => SuitesOptions -> Eff es (Either Text Text)
showSuites opts =
    case validated of
        Left err -> pure $ case opts.format of
            TextOutput -> Left $ "Error: " <> err
            JsonOutput -> Json.argumentError err
        Right (targets, selection) ->
            ensureDaemonRunning opts.ifDaemonIsStopped do
                result <- awaitBuildStatus opts.wait
                case opts.format of
                    TextOutput ->
                        pure
                            $ Text.wrapTextShow result
                            $ Text.showBasePhase
                            $ Text.showSuites targets opts.testFilter selection
                    JsonOutput ->
                        pure
                            $ Json.wrapResponse
                            $ Json.showBasePhase result
                            $ Json.showSuites targets opts.testFilter selection
  where
    validated = do
        targets <- traverse parseTarget opts.suiteNames
        selection <- FieldMask.Schema.select schema $ fromMaybe defaultFields opts.fields
        pure (targets, selection)
    parseTarget suiteName =
        maybeToRight
            ( "invalid test suite name: "
                <> suiteName
                <> ". Test suite names are expected to be in the format of a Cabal target."
            )
            $ TestTarget.parse suiteName


awaitBuildStatus
    :: ( File :> es
       , Reader SocketPath :> es
       , UnixSocket :> es
       )
    => WaitMode -> Eff es (Either Text BuildState)
awaitBuildStatus wait = do
    SocketPath sockPath <- Reader.ask
    case wait of
        WaitForBuild -> queryStatusWait sockPath
        ShowCurrent -> queryStatus sockPath


ensureDaemonRunning
    :: ( Daemons :> es
       , Delay :> es
       , IOE :> es
       , Reader PidFile :> es
       , Reader SocketPath :> es
       , UnixSocket :> es
       )
    => IfDaemonIsStopped -> Eff es (Either Text Text) -> Eff es (Either Text Text)
ensureDaemonRunning ifDaemonIsStopped continue = do
    isRunning <- Client.isDaemonRunning
    case (ifDaemonIsStopped, isRunning) of
        (IfDaemonIsStopped.Fail, False) -> pure $ Left "daemon is stopped"
        (IfDaemonIsStopped.StartDaemon, False) -> do
            ensureDaemonIsRunning
            continue
        (_, True) -> continue


ensureDaemonIsRunning
    :: ( Daemons :> es
       , Delay :> es
       , IOE :> es
       , Reader PidFile :> es
       , Reader SocketPath :> es
       , UnixSocket :> es
       )
    => Eff es ()
ensureDaemonIsRunning = do
    Daemons.startDaemon
    void Daemons.waitForDaemon