REPL
napkin repl
starts GHCI session that is preconfigured for Spec development. It can be used to run Spec and Hook programs interactively for easier debugging. In addition to configuring GHCI session with appropriate language extensions, search path and packages, Napkin will define helper macros that make running custom spec and hook programs easier.
runSpecProgram
:runSpecProgram
macro can be used to run any SpecProgram' backend returnValue
interactively. Note that programs used to create tables have a type of SpecProgram backend
which forces return type to be ()
. However, for interactive runs, one may return any value. Returned value will be bound to result
This macro accepts the target table name and the program as arguments.
napkin-repl
import SomeModule
:runSpecProgram "target.table.name" SomeModule.myProgram
Program can be inlined as well (make sure to use quotes or $
operator)
napkin-repl
:runSpecProgram "target.table.name" (logInfo "I am a spec program" >> pure "some return value")
or
napkin-repl
:runSpecProgram "target.table.name" $ logInfo "I am a spec program" >> pure "some return value"
napkin-repl> :runSpecProgram "target.table.name" $ logInfo "I am a spec program" >> pure "some return value"
[2022-01-13 11:46:31][Info] I am a spec program
Right "some return value"
napkin-repl> :type result
result
:: Either
Napkin.Run.Effects.Languages.NapkinError.NapkinEffectError [Char]
napkin-repl> result
Right "some return value"
runHookProgram
:runHookProgram
macro allows to run arbitrary HookProgram' backend returnValue
. Similarly to runSpecProgram
, REPL helper allows to return value.
napkin-repl
:runHookProgram (logInfo "I am a hook program" >> pure 0x1234)
napkin-repl> :runHookProgram (logInfo "I am a hook program" >> pure 0x1234)
[2022-01-13 11:52:14][Info] I am a hook program
Right (Right ([],4660))
napkin-repl> :type result
result
:: Either
Napkin.Run.Effects.Languages.NapkinError.NapkinEffectError
(Either
(GHC.Base.NonEmpty
Napkin.Run.Effects.Languages.Assertion.AssertionEntry)
([Napkin.Run.Effects.Languages.Assertion.AssertionEntry], Integer))
napkin-repl> result
Right (Right ([],4660))
runCustomSpec and runCustomHook
:runCustomSpec
and :runCustomHook
macros allow to run SpecProgramWithArgParser
and HookProgramWithArgParser
respectively:
-
:runCustomSpec
acceptsSpecProgramWithArgParser backend
, target table name, and aMap Text Aeson.Value
as arguments. -
:runCustomHook
acceptsHookProgramWithArgParser backend
, target table name, and aMap Text Aeson.Value
as arguments.
napkin-repl
:runCustomSpec customSpec "target.table.name" [("arg", A.String "foo")]
napkin-repl
:runCustomHook customHook [("arg", A.String "foo")]
:{
let customHook :: HookProgramWithArgParser b
customHook = HookProgramWithArgParser $ \obj -> do
arg <- obj .: "arg"
pure $ do
logInfo $ "The arg was: " <> arg
:}
napkin-repl> :runCustomHook customHook [("arg" .= "Hello world!")]
[2022-01-13 12:00:25][Info] The arg was: Hello world!
Right (Right ([],()))
Example
We can define a HookProgram
with corresponding argument parser and test it directly from the REPL. For production use, we’d define them in a Haskell module and import them to REPL or reference in the Spec.
:{
let checkManyTablesExistHook :: [Ref Table] -> HookProgram b
checkManyTablesExistHook = mapM_ $ \table -> do
exists <- checkTableExists table
assertTrue ("check if table " <> refText table <> " exists") exists
customHookWithArgs :: HookProgramWithArgParser b
customHookWithArgs = HookProgramWithArgParser $ \obj -> do
tables <- obj .: "tables"
pure $ checkManyTablesExistHook tables
:}
:runCustomHook customHookWithArgs [("tables" .= ["chinook.Artist", "does_not.exist"])]
napkin-repl> :runCustomHook customHookWithArgs [("tables" .= ["chinook.Artist", "does_not.exist"])]
... some logs omitted for brevity ...
[2022-01-13 12:10:03][Debug] Executing query: SELECT
exists((SELECT 1 AS "aaac"
FROM "pg_catalog"."pg_class" AS "aaaa"
INNER JOIN "pg_catalog"."pg_namespace" AS "aaab" ON ("aaab"."oid" = "aaaa"."relnamespace")
WHERE (("aaab"."nspname" = 'chinook')) and (("aaaa"."relname" = 'Artist')))) AS "check"
[2022-01-13 12:10:03][Debug] table chinook.Artist exists
[2022-01-13 12:10:03][Debug] Executing query: SELECT
exists((SELECT 1 AS "aaac"
FROM "pg_catalog"."pg_class" AS "aaaa"
INNER JOIN "pg_catalog"."pg_namespace" AS "aaab" ON ("aaab"."oid" = "aaaa"."relnamespace")
WHERE (("aaab"."nspname" = 'does_not')) and (("aaaa"."relname" = 'exist')))) AS "check"
[2022-01-13 12:10:03][Error] table does_not.exist exists
Right (Left (AssertionEntry {assertionGroup = AssertionGroup [], assertionMessage = "table chinook.Artist exists", assertionStatus = Success, assertionSeverity = FailNow} :| [AssertionEntry {assertionGroup = AssertionGroup [], assertionMessage = "table does_not.exist exists", assertionStatus = Failure Nothing, assertionSeverity = FailNow}]))
...