Haskell Stack执行stack test时重建可执行文件遇锁阻塞求助
我正在使用Haskell和Stack开发,项目结构如下:
name: foo dependencies: - base library: source-dirs: src executables: foo: source-dirs: app main: Main.hs dependencies: - foo tests: end-to-end: main: Spec.hs source-dirs: test/end-to-end dependencies: - foo # foo:exe:foo should also be a dependency - hspec - process
这个应用是核心库的轻量封装,我希望通过端到端测试确保CLI功能符合预期。但Stack不允许将foo:exe:foo直接作为end-to-end测试的依赖。
为绕开这个限制,我尝试在test/end-to-end/Spec.hs中通过子进程调用Stack重新构建可执行文件:
build :: IO () build = void (readCreateProcess (shell "stack build foo:exe:foo") "") main :: IO () main = do build hspec ...
但程序会永久挂起,提示信息如下:
foo> blocking for directory lock on .stack-work/dist/x86_64-linux-tinfo6/Cabal-3.2.1.0/build-lock
请问问题出在哪?我哪里做错了?
我知道可以用其他工具单独处理端到端测试,但这样就得用Stack跑单元测试,另一个工具跑端到端,远不如直接执行stack test一键运行所有测试方便。
问题根源
当你执行stack test时,Stack已经持有了项目的构建锁——这个锁用来保证同一时间只有一个构建进程操作项目的.stack-work目录。而你在测试代码里又调用stack build,子进程会尝试获取同一个锁,最终导致死锁:父进程(测试进程)持有锁等待子进程完成,子进程等待父进程释放锁,双方互相卡住。
正确解决方案
不需要在测试里手动构建可执行文件,Stack本身提供了关联可执行文件和测试的机制:
1. 修改Stack配置,关联可执行文件依赖
在package.yaml(或对应的.cabal文件)的tests部分,添加build-tools(或新版本的executable-depends)字段,明确测试依赖的可执行文件:
tests: end-to-end: main: Spec.hs source-dirs: test/end-to-end dependencies: - foo - hspec - process build-tools: foo:exe:foo
这样配置后,执行stack test时,Stack会自动先构建foo:exe:foo,再运行端到端测试。
2. 在测试中调用可执行文件
不需要手动构建,直接用stack exec来启动你的CLI程序,这样能确保调用的是最新构建的版本,且跨平台兼容:
import System.Process (readCreateProcess, shell) import Test.Hspec main :: IO () main = hspec $ do describe "CLI功能验证" $ do it "处理参数后输出正确结果" $ do output <- readCreateProcess (shell "stack exec foo -- --test-arg") "" output `shouldBe` "预期输出内容\n"
3. 避免手动构建操作
移除测试代码中的build函数调用,Stack会自动处理依赖的构建顺序,既不会出现锁冲突,也能保证测试用的是最新代码。
内容的提问来源于stack exchange,提问作者Latour

