为FFI测试生成带状态函数指针及QuickCheck应用技术问询
用QuickCheck生成可显示的C风格有状态外部函数
需求概要
需要生成符合C签名T f()的有状态函数,作为外部函数的参数,同时要求函数(或其内部的s -> (T,s)状态转换逻辑)具备可显示特性;已掌握无状态外部函数的包装方法,但无法将Haskell的State monad转换为C风格的隐藏状态函数,同时希望用QuickCheck生成这类有状态函数来测试接收它们的C函数(如示例中的twice)。
已知无状态实现参考
type Compare = CInt -> CInt -> CBool foreign import ccall "wrapper" mkCompare :: Compare -> IO (FunPtr Compare)
有状态函数示例对比
C侧有状态函数
static int i = 0; int f() { return i++; }
Haskell等价表示
State (\s -> (s, s+1))
C侧测试函数
int twice(int (*f)()) { f(); return f(); }
解决方案实现
1. 定义可显示的状态转换器类型
先封装状态转换逻辑,实现Show实例保证可显示性:
import Test.QuickCheck import Control.Monad.State import Foreign.C.Types import Foreign.Ptr import Foreign.ForeignPtr import Foreign.Marshal.Alloc -- 包装状态转换逻辑:输入旧状态,输出(返回值, 新状态) newtype StatefulFunc s = StatefulFunc { runStateful :: s -> (CInt, s) } -- 实现Show,让状态转换器可被QuickCheck展示 instance Show s => Show (StatefulFunc s) where show (StatefulFunc f) = "StatefulFunc " ++ show f
2. 为QuickCheck添加随机生成实例
针对CInt状态生成各种有状态逻辑:
-- 生成随机的状态转换逻辑:比如加减随机数、取模等 instance Arbitrary (StatefulFunc CInt) where arbitrary = do delta <- arbitrary :: Gen CInt -- 基础逻辑:返回当前状态,状态累加delta return $ StatefulFunc $ \s -> (s, s + delta) -- 可扩展复杂逻辑: -- cond <- arbitrary -- return $ StatefulFunc $ \s -> if cond then (s * 2, s + 1) else (s - 1, s * 3)
3. 包装为C可调用的FunPtr
用ForeignPtr管理全局状态,模拟C的static变量:
-- 将Haskell状态函数包装为C可调用的函数指针 mkStatefulFunc :: StatefulFunc CInt -> IO (FunPtr (IO CInt)) mkStatefulFunc (StatefulFunc f) = do -- 初始化状态内存(这里用0作为初始值,也可随机生成) stateFPtr <- mallocForeignPtr withForeignPtr stateFPtr $ \ptr -> poke ptr (0 :: CInt) -- 包装成C兼容的函数:每次调用读写状态 wrapper $ do currentState <- withForeignPtr stateFPtr $ \ptr -> peek ptr let (result, newState) = f currentState withForeignPtr stateFPtr $ \ptr -> poke ptr newState return result where -- 声明对应C签名的wrapper foreign import ccall "wrapper" wrapper :: IO CInt -> IO (FunPtr (IO CInt))
4. 集成QuickCheck测试
编写属性测试验证C函数的行为:
-- 导入C的twice函数 foreign import ccall "twice" c_twice :: FunPtr (IO CInt) -> IO CInt -- QuickCheck属性:验证twice调用后的返回值符合有状态函数的预期 prop_twice_stateful :: StatefulFunc CInt -> Property prop_twice_stateful sf = ioProperty $ do fPtr <- mkStatefulFunc sf -- 计算预期值:模拟两次调用状态转换 let (_, firstNewState) = runStateful sf 0 (expectedRet, _) = runStateful sf firstNewState actualRet <- c_twice fPtr freeHaskellFunPtr fPtr return $ actualRet == expectedRet -- 运行测试 main :: IO () main = quickCheck prop_twice_stateful
内容的提问来源于stack exchange,提问作者Johannes Riecken
相关产品推荐
相关产品推荐

