Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/Python/Inline/QQ.hs
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ pyf = QuasiQuoter
}

-- | Create quote of python code suitable for use with
-- 'Python.Inline.Eval.exec'
-- 'Python.Inline.Eval.exec', 'Python.Inline.Eval.eval',
-- 'Python.Inline.Eval.evalPyFunction'.
--
-- It creates value of type @PyQuote@
-- It creates value of type 'Python.Inline.Eval.PyQuote'.
--
-- @since 0.2@
pycode :: QuasiQuoter
Expand Down
11 changes: 2 additions & 9 deletions src/Python/Internal/EvalQQ.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
) where

import Control.Monad.IO.Class
import Control.Monad.Catch

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.4.8 / 3.12 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.12 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.13 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.14 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.8.4 / 3.12 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.10 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.6.6 / 3.12 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.14.1 / 3.12 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.12 / -fpython3-config

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.12.2 / 3.12 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.12 /

The import of ‘Control.Monad.Catch’ is redundant

Check warning on line 11 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.11 /

The import of ‘Control.Monad.Catch’ is redundant
import Data.Bits
import Data.Char
import Data.List (intercalate)
Expand All @@ -29,7 +29,7 @@
import Python.Internal.Types
import Python.Internal.Program
import Python.Internal.Eval
import Python.Internal.CAPI

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.4.8 / 3.12 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.12 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.13 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.14 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.8.4 / 3.12 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.10 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.6.6 / 3.12 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.14.1 / 3.12 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.12 / -fpython3-config

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.12.2 / 3.12 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.12 /

The import of ‘Python.Internal.CAPI’ is redundant

Check warning on line 32 in src/Python/Internal/EvalQQ.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / GHC 9.10.2 / 3.11 /

The import of ‘Python.Internal.CAPI’ is redundant
import Python.Inline.Literal


Expand Down Expand Up @@ -88,8 +88,7 @@
-> TH.Q TH.Exp
expQQ mode qq_src = do
-- We need to preprocess before passing it to python.
let src = prepareSource mode qq_src
src_var = prepareForVarLookup mode src
let src = prepareSource mode qq_src
antis <- liftIO $ do
-- We've embedded script into library and we need to pass source
-- code of QQ to a script. It can contain whatever symbols so to
Expand All @@ -105,7 +104,7 @@
, "decode_and_print('" <>
concat [ [ intToDigit $ fromIntegral (w `shiftR` 4)
, intToDigit $ fromIntegral (w .&. 15) ]
| w <- BS.unpack $ T.encodeUtf8 $ T.pack src_var
| w <- BS.unpack $ T.encodeUtf8 $ T.pack src
]
<> "')"
]
Expand Down Expand Up @@ -140,12 +139,6 @@
Exec -> unindent
Fun -> unindent

prepareForVarLookup :: Mode -> String -> String
prepareForVarLookup = \case
Eval -> id
Exec -> id
Fun -> ("def __dummy__():\n"++) . indent

prepareForEval :: Mode -> [String] -> String -> String
prepareForEval mode vars src = case mode of
Eval -> src
Expand Down
174 changes: 100 additions & 74 deletions test/TST/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ tests :: TestTree
tests = testGroup "Run python"
[ testCase "Empty QQ" $ runPy [py_| |]
, testCase "Second init is noop" $ initializePython
, testCase "Nested runPy" $ runPy $ liftIO $ runPy $ pure ()
, testRunPy
, testScope
, testEvalExec
, testMonadic
, testAsync
]

-- Test that python evaluation works in general and its interaction
-- with exceptions
testRunPy :: TestTree
testRunPy = testGroup "Running python"
[ testCase "Nested runPy" $ runPy $ liftIO $ runPy $ pure ()
, testCase "Nested runPyInMain" $ runPyInMain $ liftIO $ runPyInMain $ pure ()
, testCase "runPyInMain" $ runPyInMain $ [py_|
import threading
Expand All @@ -44,7 +55,12 @@ tests = testGroup "Run python"
$ do liftIO $ putMVar lock ()
liftIO $ threadDelay 10_000_000
error "Should be interrupted"
, testCase "Scope pymain->any" $ runPy $ do
]

-- Test that different quasiquotes correctly handle scoping
testScope :: TestTree
testScope = testGroup "Python scope"
[ testCase "Scope pymain->any" $ runPy $ do
[pymain|
x = 12
x
Expand Down Expand Up @@ -135,7 +151,11 @@ tests = testGroup "Run python"
except NameError:
pass
|]
, testCase "pyf works" $ do
]

testEvalExec :: TestTree
testEvalExec = testGroup "eval/exec"
[ testCase "pyf works" $ do
let x = 12 :: Int
eq (Just (482412::Int)) [pyf|
xs = [i*x_hs for i in [1, 200, 40000]]
Expand Down Expand Up @@ -166,79 +186,85 @@ tests = testGroup "Run python"
assert m_hs.a == 12
assert m_hs.b == 'asd'
|]
, testGroup "async" $ guardThreaded
[ -- We can run async computation at all
testCase "runPyAsync" $ do
runPy [pymain| dct = {} |]
a <- runPyAsync $ [py_| dct[1] = 100 |]
_ <- atomically $ waitPy a
n <- runPy $ fromPy =<< [pye| dct[1] |]
assertEqual "x" (Just (100::Int)) n
runPy [pymain| del dct |]
, -- Cancellation of python code
testCase "cancelPy [python]" $ do
a <- runPyAsync $ forever $ [py_|
import time
while True:
time.sleep(1e-3)
|]
d <- registerDelay 100_000
threadDelay 100 -- Wait to make sure execution actually started
cancelPy a
_ <- atomically $ waitPyCatch a `orElse` do readTVar d >>= \case
True -> error "Timeout"
False -> retry
return ()
, -- Cancellation of haskell code
testCase "cancelPy [haskell]" $ do
a <- runPyAsync $ do
liftIO $ forever $ threadDelay 1_000_000
d <- registerDelay 100_000
threadDelay 100
cancelPy a
_ <- atomically $ waitPyCatch a `orElse` do readTVar d >>= \case
True -> error "Timeout"
False -> retry
return ()
, -- Cancellation of haskell code
testCase "cancelPy [callback]" $ do
a <- runPyAsync $ do
let loop = forever $ threadDelay 1_000_000 :: IO ()
forever [py_| loop_hs() |]
d <- registerDelay 100_000
threadDelay 100
_ <- forkIO $ cancelPy a
_ <- atomically $ waitPyCatch a `orElse` do readTVar d >>= \case
True -> error "Timeout"
False -> retry
return ()
, testCase "Exception in runPyInMain works py" $ do
lock <- newEmptyMVar
tid <- myThreadId
_ <- forkIO $ takeMVar lock >> threadDelay 1000 >> throwTo tid Stop
handle (\Stop -> pure ())
$ runPyInMain
$ do liftIO $ putMVar lock ()
[py_|
import time
while True:
time.sleep(1e-3)
|]
error "Should be interrupted"
]
-- Here we only test that quasiquotes produce correct code
, testGroup "Monadic"
[ testCase "pymain" $ runPy [pymain| assert True |]
, testCase "py_" $ runPy [py_| assert True |]
, testCase "pye" $ runPy $ do
n <- fromPy =<< [pye| 42 |]
liftIO $ Just (42::Int) @=? n
, testCase "pyf" $ runPy $ do
n <- fromPy =<< [pyf| return 42 |]
liftIO $ Just (42::Int) @=? n
]
]

testAsync :: TestTree
testAsync = testGroup "async" $ guardThreaded
[ -- We can run async computation at all
testCase "runPyAsync" $ do
runPy [pymain| dct = {} |]
a <- runPyAsync $ [py_| dct[1] = 100 |]
_ <- atomically $ waitPy a
n <- runPy $ fromPy =<< [pye| dct[1] |]
assertEqual "x" (Just (100::Int)) n
runPy [pymain| del dct |]
, -- Cancellation of python code
testCase "cancelPy [python]" $ do
a <- runPyAsync $ forever $ [py_|
import time
while True:
time.sleep(1e-3)
|]
d <- registerDelay 100_000
threadDelay 100 -- Wait to make sure execution actually started
cancelPy a
_ <- atomically $ waitPyCatch a `orElse` do readTVar d >>= \case
True -> error "Timeout"
False -> retry
return ()
, -- Cancellation of haskell code
testCase "cancelPy [haskell]" $ do
a <- runPyAsync $ do
liftIO $ forever $ threadDelay 1_000_000
d <- registerDelay 100_000
threadDelay 100
cancelPy a
_ <- atomically $ waitPyCatch a `orElse` do readTVar d >>= \case
True -> error "Timeout"
False -> retry
return ()
, -- Cancellation of haskell code
testCase "cancelPy [callback]" $ do
a <- runPyAsync $ do
let loop = forever $ threadDelay 1_000_000 :: IO ()
forever [py_| loop_hs() |]
d <- registerDelay 100_000
threadDelay 100
_ <- forkIO $ cancelPy a
_ <- atomically $ waitPyCatch a `orElse` do readTVar d >>= \case
True -> error "Timeout"
False -> retry
return ()
, testCase "Exception in runPyInMain works py" $ do
lock <- newEmptyMVar
tid <- myThreadId
_ <- forkIO $ takeMVar lock >> threadDelay 1000 >> throwTo tid Stop
handle (\Stop -> pure ())
$ runPyInMain
$ do liftIO $ putMVar lock ()
[py_|
import time
while True:
time.sleep(1e-3)
|]
error "Should be interrupted"
]


-- Here we only test that quasiquotes produce correct code
testMonadic :: TestTree
testMonadic = testGroup "Monadic"
[ testCase "pymain" $ runPy [pymain| assert True |]
, testCase "py_" $ runPy [py_| assert True |]
, testCase "pye" $ runPy $ do
n <- fromPy =<< [pye| 42 |]
liftIO $ Just (42::Int) @=? n
, testCase "pyf" $ runPy $ do
n <- fromPy =<< [pyf| return 42 |]
liftIO $ Just (42::Int) @=? n
]


data Stop = Stop
deriving stock Show
deriving anyclass Exception
Expand Down
Loading