Home > Mobile >  Generate stateful function pointer for FFI testing
Generate stateful function pointer for FFI testing

Time:11-04

I want to generate stateful functions (C signature T f()) with QuickCheck as arguments for foreign functions. Preferably I also want to make them (or their inner s->(T,s)) showable.

I know that for stateless functions I can write something like

type Compare = CInt -> CInt -> CBool
foreign import ccall "wrapper"
  mkCompare :: Compare -> IO (FunPtr Compare)

but I got stuck trying this approach for the stateful functions, as I don't see how I can translate the monad hiding the state in Haskell to the function hiding the state in C.

Example of a stateful function f

static int i = 0;

int f() {
    return i  ;
}

In Haskell I would represent this function as State (\s -> (s,s 1)).

What does QuickCheck have to do with it?

If I have a C function that takes a stateful function as argument, e.g.

int twice(int (*f)()) {
    f();
    return f();
}

then I can test the function with QuickCheck, where QuickCheck can generate different implementations for f, which would usually look similar to

prop_total (Fun f) xs = total $ g f xs

but these generated functions are stateless, not stateful like the example C function above.

CodePudding user response:

Thanks to Daniel Wagner's suggestion in the comments, I could figure it out. The Show instance comes for free. Here's a minimal example that I ran with

gcc -fPIC -shared -o libstateful.dylib stateful.c && ghc -L. Stateful.hs -lstateful && ./Stateful

As expected it will output a distribution of about 50% of 1 (True) and 50% of 0 (False).

Stateful.hs

import Data.IORef
import Foreign.C
import Foreign.Ptr
import System.IO.Unsafe
import Test.QuickCheck

instance CoArbitrary CInt where
  coarbitrary = coarbitraryIntegral

instance Arbitrary CBool where
  arbitrary = chooseEnum (0,1)
  shrink 1 = [0]
  shrink 0 = []

instance Function CInt where
  function = functionIntegral

type Generator = IO CBool

foreign import ccall "wrapper" mkGenerator :: Generator -> IO (FunPtr Generator)

foreign import ccall "changes" changes :: FunPtr Generator -> IO CBool

type StateFn = CInt -> (CBool,CInt)

stateFnToIORef :: StateFn -> IORef CInt -> IO CBool
stateFnToIORef f s_ref = do
    s <- readIORef s_ref
    let (a,s') = f s
    writeIORef s_ref s'
    pure a

prop_changes :: Fun CInt (CBool,CInt) -> Property
prop_changes (Fn f) = unsafePerformIO (do
    x_ref <- newIORef 0
    f' <- mkGenerator $ stateFnToIORef f x_ref
    res <- changes f'
    pure $ collect res (total res))

main :: IO ()
main = quickCheck prop_changes

stateful.c

_Bool changes(_Bool (*f)()) {
    _Bool x = f();
    _Bool y = f();
    return x != y;
}
  • Related