content: -- |
  -- Tests for variable scope and names
  module TST.Run(tests) where
  
  import Control.Concurrent
  import Control.Exception
  import Control.Monad
  import Control.Monad.IO.Class
  import Data.Map.Strict        qualified as Map
  import Test.Tasty
  import Test.Tasty.HUnit
  import Python.Inline
  import Python.Inline.QQ
  import Python.Inline.Eval
  import TST.Util
  
  tests :: TestTree
  tests = testGroup "Run python"
    [ testCase "Empty QQ" $ runPy [py_| |]
    , testCase "Second init is noop" $ initializePython
    , testCase "Nested runPy" $ runPy $ liftIO $ runPy $ pure ()
    , testCase "Nested runPyInMain" $ runPyInMain $ liftIO $ runPyInMain $ pure ()
    , testCase "runPyInMain" $ runPyInMain $ [py_|
        import threading
        assert threading.main_thread() == threading.current_thread()
        |]
    , testCase "Python exceptions are converted (py)"   $ runPy      $ throwsPy    [py_| 1 / 0 |]
    , testCase "Python exceptions are converted (std)"  $ throwsPyIO $ runPy       [py_| 1 / 0 |]
    , testCase "Python exceptions are converted (main)" $ throwsPyIO $ runPyInMain [py_| 1 / 0 |]
    , testCase "Main doesn't deadlock after exception"  $ do
        throwsPyIO $ runPyInMain [py_| 1 / 0 |]
        runPyInMain [py_| assert True |]
      -- Here we test that exceptions are really passed to python's thread without running python
    , testCase "Exception in runPyInMain works" $ do
        lock <- newEmptyMVar
        tid  <- myThreadId
        _    <- forkIO $ takeMVar lock >> throwTo tid Stop
        handle (\Stop -> pure ())
          $ runPyInMain
          $ do liftIO $ putMVar lock ()
               liftIO $ threadDelay 10_000_000
               error "Should be interrupted"
        runPyInMain $ pure ()
    --
    , testCase "Scope pymain->any" $ runPy $ do
        [pymain|
               x = 12
               x
               |]
        -- Visible
        [py_| x |]
        _ <- [pye| x |]
        [pymain|
          x
          del x
          |]
        -- Disappears
        [pymain|
          try:
              x
              assert False, "x shouln't be visible"
          except NameError:
              pass
          |]
        [py_|
          try:
              x
              assert False, "x shouln't be visible"
          except NameError:
              pass
          |]
    , testCase "Scope py_->any" $ runPy $ do
        [py_|
          x = 12
          x
          |]
        -- Not visible
        throwsPy $ void [pye| x |]
        [py_|
          try:
              x
              assert False, "x shouln't be visible (1)"
          except NameError:
              pass
          |]
        [pymain|
          try:
              x
              assert False, "x shouln't be visible (2)"
          except NameError:
              pass
          |]
    , testCase "Import py_->any" $ runPy $ do
        [py_|
          import sys
          sys
          |]
        -- Not visible
        throwsPy $ void [pye| sys |]
        [py_|
          try:
              sys
              assert False, "sys shouln't be visible (1)"
          except NameError:
              pass
          |]
        [pymain|
          try:
              sys
              assert False, "sys shouln't be visible (2)"
          except NameError:
              pass
          |]
    , testCase "Scope pyf->any" $ runPy $ do
        _ <- [pyf|
          x = 12
          x
          return 12
          |]
        -- Not visible
        throwsPy $ void [pye| x |]
        [py_|
          try:
              x
              assert False, "x shouln't be visible (1)"
          except NameError:
              pass
          |]
        [pymain|
          try:
              x
              assert False, "x shouln't be visible (2)"
          except NameError:
              pass
          |]
    , testCase "pyf works" $ do
        let x = 12 :: Int
        eq (Just (482412::Int)) [pyf|
           xs = [i*x_hs for i in [1, 200, 40000]]
           return sum(xs)
           |]
    , testCase "exec with Dict" $ runPy $ do
        dct <- [pye| {} |]
        exec Main (Dict dct) [pycode|
          a = 12
          b = 13
        |]
        throwsPy $ exec Main (Module dct) [pycode| |]
        d <- fromPy dct
        liftIO $ assertEqual "dict" (Just (Map.fromList [("a",12::Int),("b",13)])) d
    , testCase "exec with Module" $ runPy $ do
        m <- [pyf|
          import importlib.util
          spec = importlib.util.spec_from_loader("dyn", loader=None)
          return importlib.util.module_from_spec(spec)
          |]
        exec Main (Module m) [pycode|
          a = 12
          b = 'asd'
          |]
        [py_|
          import types
          isinstance(m_hs, types.ModuleType)
          assert m_hs.a == 12
          assert m_hs.b == 'asd'
          |]
    ]
  
  data Stop = Stop
    deriving stock    Show
    deriving anyclass Exception
  