How many kernel test frameworks?
By Jake Edge
June 5, 2019
Linux kernel的自测试框架名为kselftest,已经加入kernel一段时间了。最近又有一个新的kernel unit-testing framework(KUnit)提出来,那么为什么要有两种方案共存呢?关于KUnit有一个邮件讨论话题经过了很长的讨论,大家都在争论为什么要在kernel里增加另一个测试框架。虽然kselftest和KUnit的使用场景并不一样,不过人们还是有些担心两者共存会导致内核测试整体工作的分裂。
5月上旬Brendan Giggins推送了KUnit patch set的第二版,期待能合入 Linux 5.2。Greg Kroah-Hartman和 Shuah Khan 觉得这个可能有点时间太紧,毕竟merge window还有1周时间。不过Khan也赞同这组patch应该从她的kselftest tree这边来进入Linux kernel。这组patch里面有一些细节问题还要解决,不过整体来说已经拿到了approval和不少开发者的reviewed-by签字。
不过,Korah-Hartman和Logan Gunthorpe等人都还在抱怨KUnit对user-mode Linux (UML)的依赖。Higgins回复说“基本上解决了”。KUnit现在可以在任何体系架构上运行,不过在UML里面测试的话Python wrapper script还是需要的。他打算仔细写个文档描述清楚(后来他也确实发出来文档了)。
另一个问题是Frank Rowand提出的。从他角度来看,使用UML就意味着“避免在真实硬件或虚拟机里启动kernel”。当然,他也觉得这并不是不可以,毕竟用UML来跑Linux也就是另外一种意义上的虚拟化而已。不过,他更不喜欢的是:
KUnit对我来说就是作为kernel developer必须要去熟悉的另一个框架,带来更多麻烦,也要给我的小脑瓜里面塞更多信息进去。
我猜有些开发者估计只会尽量利用两种框架之一,这样最终会导致开发资源的分裂,还不如把所有人都引向唯一一个测试框架。
Khan的回复中讲,她认为kselftest和KUnit是可以互补的。Kselftest是“一组user-space测试的集合,在部分case里依赖一些kernel test module”。而KUnit就提供了一种in-kernel的测试框架。
不过Rowand没被Khan的说法说服,他觉得两个框架大部分是重复的。
与众不同的是,Ted Ts'o确实有经历证明UML测试会有帮助。他介绍过有一些针对ext4的unit test正在开发过程中,它们会能够对ext4的一些功能在独立于kernel其他部分的环境下进行测试,因此他倒觉得用UML测试会很有帮助。kselftest框架里的测试都是针对user space里运行的测试,这需要先启动一个真实的kernel。而KUnit就很简单,也能很快用起来:
所以,这就是为什么我觉得KUnit用UML不是什么问题。事实上我更觉得这是一个好功能。我们不是在测试设备驱动程序,或者内核调度器,或者任何跟体系架构有关的东西。UML并不是虚拟化。它在这个情景下能让我们尽快运行起测试代码来。启动KVM需要3~4秒时间,还包含初始化virtio_scsi和其他设备驱动。如果用UML,我们可以把其他不相干的kernel子系统尽量裁剪,裁到极致。此外,它也意味着我们能让userspace的"printf"直接跟测试框架交流,而不需要跟KVM的虚拟串口或者虚拟控制台通讯,这样搭建测试环境的花销更加能大大减少了。
Frameworks
大家的意见分歧,可能部分来源于怎么定义framework(框架)。Ts'o坚持认为kselftest提供的不是一个in-kernel的测试框架,而Rowand也强烈反对这个说法。Rowand提出kselftest里面用到了kernel module,并且这些module也是能编译进UML kernel的。Ts'o不认为这个算是framework,毕竟“每个in-kernel的代码段要想测试都需要自己创建自己的in-kernel测试架构”。Rowand不这么看,他觉得:“kselftest的in-kernel测试都是遵循统一规范的,因此这就是一个测试框架”。对Ts'o来说,这算不上是一个框架,他评论说:
我们可能对framework的定义不太一样,在我的理解里,通过cut and paste来进行的代码重用不是一个framework。今后如果把这部分测试重写,真正利用KTF或者KUnit这样的framework,那也可以。不过它们现在并没有用真正的framework。
此外,Ts'o还指出kselftest需要有一个可用的user-space环境:
有一个主要的区别:kselftest需要一个userspace环境,它先启动systemd,依赖一个root file system这样才能加载module,等等等等。而Kunit就不需要root file system,也不需要启动systemd,也不允许你去跑各种perl/python/bash等等脚本。
Rowand仍然不赞成:
Kselftest in-kernel test(我们目前讨论的是这个场景)就可以被编译进kernel,而不是编译成module,也可以编译进UML kernel。这样UML kernel就能启动、然后在UML调用init进程之前就先开始跑in-kernel test。
不需要有userspace环境,因此同KUnit的调用场景比起来开销并不多。
Ts'o仍然不放弃,他认为kselftest的文档从来没有提起这种测试方式。而且在init进程启动之前,有其他测试可以跑,不过并不是kselftest framework的一部分:
init执行之前kernel是有test modules可以运行——不过严格来说这并不是kselftests的一部分,也没有任何架构性的支持。而kselftests_harness头文件里面明确写了你的test case应该运行在userspace。
Overlaps
KUnit和kselftest的功能也可能有一些重复。Knut Omang是Kernel Test Framework项目的一员,认为讨论中大家把两种不通的测试混在一起讨论了。一种是独立测试某个特定子系统的,这个子系统的开发者需要能快速并且重复运行这种测试;另一种是需要测试多个子系统的互相交互,通常应用于regression test suite里,或者continuous-integration的场景,当然开发者也会进行这种测试。ext4专门的unit test应该算是第一类,而xfstests就算是第二种类型。
Omang认为两类测试应该还是能够合并进同一个测试 工具 的,使用通用的configration文件,测试报告格式,等等。这就是KTF打算做的事情。不过Ts'o很怀疑可以用一个测试框架来一路走下去。目前已经有多种测试框架存在了,包括xfstests, blktests, kselftest等等。Omang还觉得UML在这种场景下把水搅得更混了:
使用UML的主要问题是你还是逃不掉启动复杂的kernel运行时环境的,而你真正需要的测试其实只是在user space上下文编译几个kernel源代码文件,然后用Valgrind等user space工具来检查这段代码。这里主要挑战是怎样在这种环境里编译好那几个源代码文件,因为通常它们编译时都会依赖一堆内核宏定义。这就是为什么你们退而求其次来找到UML。
不过Ts'o看法不同:
在user land编译几个kernel源文件并不难,只要写一堆stub函数即可。对文件系统来说,我需要虚拟出block device layer,还有VFS层的大部分函数,scheduler和内核锁等相关函数,等等。实际上,UML就像是这堆stub函数。所以Frank说的KUnit没有提供任何支持,这一点我不同意。KUnit和ML配合着用,让我们测试内部接口的时候简单的多了,特别是相比于把这些内核源代码编译成userspace test程序来说,简单的多得多。
Gunthorpe也觉得有一些功能重复,他区分测试类型的思路跟Omang很类似,他认为没有太多用户会使用kselftest_harness.h这里的接口,因此是应该考虑把两者重复的部分统一起来:
虽然有人不赞成,不过KUnit确实跟kselftest有很大功能重复。无论是在轻量级UML环境里跑短小的test case,还是在重量级的VM环境里跑一些偏上层的测试,最好都能用相同的框架来写in-kernel tests。对部分人来说,能在重量级的VM环境里跟其他上层测试同时运行所有UML测试,可能也会觉得很有用处。
看一下现有的selftest代码,可以看到一些Kunit正在添加的test。kselftest_harness.h内部的EXPECT_*和ASSERT_* 这些宏定义就跟KUNIT_EXPECT_*和KUNIT_ASSERT_*定义非常类似。
Ts'o不反对把两个框架统一起来,不过他认为kselftest_harness.h需要重构一下,in-kernel test才能够开始用它。Gunthorpe后来回复的时候似乎改变了一些想法,他觉得统一两种场景的花费太大可能不值得了:
用kunit做in-kernel test,用kselftest_harness做userspace test似乎是一个比较合理的分界线。要把kernel和userspace统一起来感觉比较难,可能并不值得。除非有人愿意做出很漂亮的工作来实现出来。
最后,Rowand想要看到一个KUnit为什么有必要存在的证明,以及它跟kselftest有什么不同。Higgins想要一些具体的建议,关于KUnit缺少什么部分。Rowand回复指出patch里要写清楚正当理由:
讨论下来有一点应该很明确了,部分开发者并不理解kselftest包含in-kernel test,以为它只有userspace tests。因此,KUnit其实是相同功能的第二种实现方式。(可能会有争论kselftest和KUnit提供了那些in-kernel test功能,以及有多少是重复的。所以这里我说的相同功能可能并不是最终结论)因此需要把这个关键信息记录下来在patch内解释清楚。
不过Gunthorpe仍然不同意:“在我看来,Brendan已经提供了足够多的信息来讲明Kunit的必要性”。关于kselftest是否提供了in-kernel的framework似乎变成了双方争论的难点。Ghunthorpe相信in-kernel kselftest代码应该被改成使用KUnit,他非常期待看到这样的改动。
讨论进入尾声,Higgins在5月13日贴出了v3版本的patch set,几天后又更新到了v4版本。他解决了v2里人们提及的一些代码问题,也增加了文档介绍了如何在UML之外的体系架构上运行测试。这次没有太多反对意见,很可能KUnit能够在5.3版本里合入。