-- | A module containing functions for creating and managing tests.
module Test
  ( -- * Organizing Tests
    Internal.Test,
    Internal.test,
    Internal.describe,
    Internal.skip,
    Internal.only,
    Internal.todo,

    -- * Fuzz Testing
    Internal.fuzz,
    Internal.fuzz2,
    Internal.fuzz3,

    -- * Running test
    run,
  )
where

import qualified Control.Concurrent.Async as Async
import qualified GHC.IO.Encoding
import qualified GHC.Stack as Stack
import NriPrelude
import qualified Platform
import qualified Platform.DevLog
import qualified System.Directory
import qualified System.Environment
import qualified System.IO
import qualified Task
import qualified Test.Internal as Internal
import qualified Test.Reporter.ExitCode
import qualified Test.Reporter.Junit
import qualified Test.Reporter.Logfile
import qualified Test.Reporter.Stdout
import qualified Prelude

-- | Turn a test suite into a program that can be executed in Haskell. Use like
-- this:
--
-- > module Main (main) where
-- >
-- > import qualified Test
-- >
-- > main :: IO ()
-- > main = Test.run (Test.todo "write your tests here!")
run :: Stack.HasCallStack => Internal.Test -> Prelude.IO ()
run :: Test -> IO ()
run Test
suite = do
  -- Work around `hGetContents: invalid argument (invalid byte sequence)` bug on
  -- Nix: https://github.com/dhall-lang/dhall-haskell/issues/865
  TextEncoding -> IO ()
GHC.IO.Encoding.setLocaleEncoding TextEncoding
System.IO.utf8
  LogHandler
log <- IO LogHandler
Platform.silentHandler
  (SuiteResult
results, Bool
logExplorerAvailable) <-
    IO SuiteResult -> IO Bool -> IO (SuiteResult, Bool)
forall a b. IO a -> IO b -> IO (a, b)
Async.concurrently
      (LogHandler -> Task Never SuiteResult -> IO SuiteResult
forall a. LogHandler -> Task Never a -> IO a
Task.perform LogHandler
log (Test -> Task Never SuiteResult
forall e. Test -> Task e SuiteResult
Internal.run Test
suite))
      IO Bool
isLogExplorerAvailable
  (IO () -> IO ()) -> [IO ()] -> IO ()
forall (f :: * -> *) a b. Foldable f => (a -> IO b) -> f a -> IO ()
Async.mapConcurrently_
    IO () -> IO ()
forall a. a -> a
identity
    [ SuiteResult -> IO ()
reportStdout SuiteResult
results,
      (HasCallStack => SuiteResult -> IO ()) -> SuiteResult -> IO ()
forall a. HasCallStack => (HasCallStack => a) -> a
Stack.withFrozenCallStack HasCallStack => SuiteResult -> IO ()
reportLogfile SuiteResult
results,
      SuiteResult -> IO ()
reportJunit SuiteResult
results
    ]
  if Bool
logExplorerAvailable
    then String -> IO ()
Prelude.putStrLn String
"\nRun log-explorer in your shell to inspect logs collected during this test run."
    else String -> IO ()
Prelude.putStrLn String
"\nInstall the log-explorer tool to inspect logs collected during test runs. Find it at github.com/NoRedInk/haskell-libraries."
  SuiteResult -> IO ()
Test.Reporter.ExitCode.report SuiteResult
results

reportStdout :: Internal.SuiteResult -> Prelude.IO ()
reportStdout :: SuiteResult -> IO ()
reportStdout SuiteResult
results =
  Handle -> SuiteResult -> IO ()
Test.Reporter.Stdout.report Handle
System.IO.stdout SuiteResult
results

reportLogfile :: Stack.HasCallStack => Internal.SuiteResult -> Prelude.IO ()
reportLogfile :: SuiteResult -> IO ()
reportLogfile SuiteResult
results =
  (HasCallStack => (TracingSpan -> IO ()) -> SuiteResult -> IO ())
-> (TracingSpan -> IO ()) -> SuiteResult -> IO ()
forall a. HasCallStack => (HasCallStack => a) -> a
Stack.withFrozenCallStack
    HasCallStack => (TracingSpan -> IO ()) -> SuiteResult -> IO ()
Test.Reporter.Logfile.report
    TracingSpan -> IO ()
Platform.DevLog.writeSpanToDevLog
    SuiteResult
results

reportJunit :: Internal.SuiteResult -> Prelude.IO ()
reportJunit :: SuiteResult -> IO ()
reportJunit SuiteResult
results =
  do
    [String]
args <- IO [String]
System.Environment.getArgs
    case [String] -> Maybe String
getPath [String]
args of
      Maybe String
Nothing -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
Prelude.pure ()
      Just String
path -> String -> SuiteResult -> IO ()
Test.Reporter.Junit.report String
path SuiteResult
results

getPath :: [Prelude.String] -> Maybe Prelude.String
getPath :: [String] -> Maybe String
getPath [String]
args =
  case [String]
args of
    [] -> Maybe String
forall a. Maybe a
Nothing
    String
"--xml" : String
path : [String]
_ -> String -> Maybe String
forall a. a -> Maybe a
Just String
path
    String
_ : [String]
rest -> [String] -> Maybe String
getPath [String]
rest

isLogExplorerAvailable :: Prelude.IO Bool
isLogExplorerAvailable :: IO Bool
isLogExplorerAvailable = do
  String -> IO (Maybe String)
System.Directory.findExecutable String
"log-explorer"
    IO (Maybe String) -> (IO (Maybe String) -> IO Bool) -> IO Bool
forall a b. a -> (a -> b) -> b
|> (Maybe String -> Bool) -> IO (Maybe String) -> IO Bool
forall (m :: * -> *) a value.
Functor m =>
(a -> value) -> m a -> m value
map (Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
/= Maybe String
forall a. Maybe a
Nothing)