1 Yocto 项目测试环境手册

1 欢迎

欢迎阅读 Yocto 项目测试环境手册。本手册介绍项目用于确保每个主要版本和维护版本 按预期工作的测试环境。所有测试基础设施与流程都公开可见, 社区可以随时了解正在执行哪些测试、如何执行,以及测试和项目当前的状态。 其他组织也可以借助这些流程和环境,在项目核心提供的基础上, 建立适合自己的自动化生产测试环境。

本手册仍在持续完善,最初的内容来自 README 文件与核心工程师的记录:

  • yocto-autobuilder2: README.md 详细介绍了 Yocto 项目 Autobuilder 的配置方法。 yocto-autobuilder2 仓库 提供项目的 Buildbot 控制台界面插件,以及让 Buildbot 执行项目所需测试的配置。

  • yocto-autobuilder-helper: 该仓库及其 README 包含 Autobuilder 辅助脚本和配置。 yocto-autobuilder-helper 定义了测试哪些内容以及如何执行测试的协调逻辑。 任意 CI 系统都可以利用它获取正确的源码修订、配置构建与层、执行构建并收集结果。 代码独立于具体 CI 平台,因此既可用于 Buildbot, 也可用于 Jenkins 等系统。 仓库为每个项目发布版本维护相应分支,分别定义该版本所需的测试。

1 Yocto 项目 Autobuilder 概览

Yocto 项目 Autobuilder 是项目用来定期、自动化地在支持的硬件上测试软件的 程序、工具、脚本与流程的总称。在版本开发过程中,它负责验证各项功能是否正常, 构建所有测试目标并运行相应测试。

项目使用标准上游 Buildbot 驱动集成和测试, 通过 yocto-autobuilder2 提供的控制台插件定制界面,使构建结果能够按项目需要展示。

yocto-autobuilder-helper 中的辅助脚本提供配置和任务管理功能。 这一 helper 层包含大部分构建配置,并按发布版本维护, 因此很容易为不同项目定制。它不依赖具体 CI 系统, 可用简单的 JSON 文件生成构建配置。

备注

项目使用 Buildbot 既有历史原因,也因为很多开发者熟悉 Python。 外层调度完全可以改用 Jenkins 等其他 CI 系统。

下图展示了由一个控制节点与一组工作节点组成的 Autobuilder:

../_images/ab-test-cluster.png

1 Yocto 项目测试类型概览

Autobuilder 使用以下测试验证项目的不同部分:

  • 构建测试: 改变 MACHINE、DISTRO、其他配置选项, 以及要构建的具体镜像或 world,检查这些配置能否构建。 测试覆盖不同架构、机器、发行版和初始化系统等大量组合。 Autobuilder 实际会测试数百种配置与目标。

    • 构建过程中的完整性检查: 由 insane 类发起, 用于验证构建输出是否正确。例如,生成二进制文件的 ELF 架构应与目标系统匹配, ARM 二进制文件无法在 MIPS 系统中工作。

  • 构建性能测试: 验证常见构建步骤的效率,并避免性能回退。 oe-build-perf-test 对常见场景计时。 测试运行在隔离机器上,避免其他进程干扰测量。 项目目前同时在 Fedora 和 Ubuntu 上测试, 既避免单点依赖,也确认不同发行版都能高效工作。

  • eSDK 测试: 通过以下命令发起:

    $ bitbake image -c testsdkext
    

    使用 testsdk 类与 do_testsdkext 任务。

  • 功能测试: 通过 OpenEmbedded 自测(oe-selftest) 运行各种场景测试。项目会在每个主要受支持发行版上执行 oe-selftest。

  • 镜像测试: 通过以下命令发起:

    $ bitbake image -c testimage
    

    使用 testimage 类与 do_testimage 任务。 详情见 执行自动化运行时测试。

  • 层测试: Autobuilder 能测试特定层是否与系统其余部分正常协作。 项目成员可以选择待测层,一些重要社区层也会定期测试。

  • 软件包测试: ptest 在目标机器上测试 OpenEmbedded 构建系统生成的软件包。 详情见 使用 ptest 测试软件包 和 Ptest Wiki。

  • SDK 测试: 通过以下命令发起:

    $ bitbake image -c testsdk
    

    使用 testsdk 类与 do_testsdk 任务。

  • 单元测试: 通过 bitbake-selftest 和 oe-selftest 对系统中的不同组件执行单元测试。

  • 自动升级助手: 检查软件是否有新版本,以及能否自动升级。 如果可以,它会向维护者发送包含补丁的邮件,提示可进行升级。

1 测试与代码位置的对应关系

各类测试在代码库中的位置如下:

  • bitbake-selftest:

    这些测试自成一体,验证 BitBake 及其 API,包括 fetcher。 测试位于 bitbake/lib/*/tests。

    部分测试会运行 bitbake,因此应先把 bitbake/bin 加入 PATH。 在 BitBake 仓库中运行:

    $ export PATH=$PWD/bin:$PATH
    

    然后运行自测脚本:

    $ bitbake-selftest
    

    默认只输出执行摘要。需要更多信息时,可使用详细输出选项:

    $ bitbake-selftest -v
    

    要跳过访问互联网的测试,在执行 bitbake-selftest 时设置 BB_SKIP_NETTESTS:

    $ BB_SKIP_NETTESTS=yes bitbake-selftest
    

    需要网络访问的测试主要用于验证 fetcher 模块。 若只想运行某个测试模块,在命令后加模块名即可。 例如,运行 bb.tests.data.DataExpansions:

    $ bitbake-selftest bb.tests.data.DataExpansions
    

    也可以通过完整的模块、类和测试名称运行单个测试,例如:

    $ bitbake-selftest bb.tests.data.DataExpansions.test_one_var
    

    这些测试基于 Python unittest。

  • oe-selftest:

    • 使用 OE 验证工作流,包括特定功能、任务行为和 API 单元测试。

    • 可通过 -j 指定线程数并行执行。 同一个测试类中的测试总是在同一线程运行; 要提高大量测试的并行度,可以把一个大测试类拆成多个单元。

    • 测试基于 Python unittest。

    • 测试代码位于 meta/lib/oeqa/selftest/cases/。

    • 运行全部测试:

      $ oe-selftest -a
      
    • 运行特定测试时使用以下形式,其中 testname 为具体测试名称:

      $ oe-selftest -r <testname>
      

      例如,运行 tinfoil 的 getVar API 测试:

      $ oe-selftest -r tinfoil.TinfoilTests.test_getvar
      

      也可以运行一组测试,例如全部 tinfoil 测试:

      $ oe-selftest -r tinfoil
      
  • testimage:

    • 构建并启动镜像,然后针对镜像内容运行测试。

    • 测试代码位于 meta/lib/oeqa/runtime/cases/。

    • 需要设置 IMAGE_CLASSES:

      IMAGE_CLASSES += "testimage"
      
    • 使用以下命令运行测试:

      $ bitbake image -c testimage
      
  • testsdk:

    • 构建并安装 SDK,再测试该 SDK。

    • 测试代码位于 meta/lib/oeqa/sdk/cases/。

    • 使用以下命令运行:

      $ bitbake image -c testsdk
      
  • testsdk_ext:

    • 构建并安装可扩展 SDK(eSDK),再对其执行测试。

    • 测试代码位于 meta/lib/oeqa/sdkext/cases/。

    • 使用以下命令运行:

      $ bitbake image -c testsdkext
      
  • oe-build-perf-test:

    • 运行常见使用场景,并测量操作耗时。

    • 代码位于 meta/lib/oeqa/buildperf。

    • 使用以下命令运行:

      $ oe-build-perf-test <options>
      

      命令支持结果保存位置等多项选项。 Autobuilder 辅助脚本中的 build-perf-test-wrapper 提供了从命令行调用 oe-build-perf-test 的示例。

      可用 oe-git-archive 将测试结果存入 Git 仓库。

      使用 oe-build-perf-report 可生成文本报告, 以及带性能图表的 HTML 报告。示例见 HTML 和 文本。

    • 测试用例位于 meta/lib/oeqa/buildperf/test_basic.py。

1 测试示例

本节为 测试与代码位置的对应关系 中的各类测试提供示例。

  • oe-selftest 用例位于 meta/lib/oeqa/selftest/cases。

  • bitbake-selftest 用例位于 bitbake/lib/bb/tests/。

1.5.1 bitbake-selftest

bitbake/lib/bb/tests/data.py 中的一个简单示例如下:

class DataExpansions(unittest.TestCase):
   def setUp(self):
         self.d = bb.data.init()
         self.d["foo"] = "value_of_foo"
         self.d["bar"] = "value_of_bar"
         self.d["value_of_foo"] = "value_of_'value_of_foo'"

   def test_one_var(self):
         val = self.d.expand("${foo}")
         self.assertEqual(str(val), "value_of_foo")

此例创建了基于标准 Python unittest 的 DataExpansions 测试类。类中的公共 setUp 方法为所有测试准备环境, 随后用一个简单测试确认变量展开能够得到正确值。

BitBake 自测就是常规的 Python unittest 测试。 更多编写方法见 Python unittest 文档。

1.5.2 oe-selftest

这类测试背后需要准备完整构建环境,因此更复杂。 它们包装了大多数标准对象,而不是直接使用 Python unittest。 测试本身也可以很简单,例如在 OE 构建环境中执行一个命令:

class BitbakeLayers(OESelftestTestCase):
   def test_bitbakelayers_showcrossdepends(self):
         result = runCmd('bitbake-layers show-cross-depends')
         self.assertTrue('aspell' in result.output, msg = "No dependencies were shown. bitbake-layers show-cross-depends output: %s"% result.output)

这个例子来自 meta/lib/oeqa/selftest/cases/bblayers.py。 测试类基于由 unittest.TestCase 派生的 OESelftestTestCase, 执行 bitbake-layers 并检查输出中是否包含预期内容。

oeqa.utils.commands 提供了处理常见任务的辅助函数:

  • 读取 BitBake 变量: 使用 oeqa.utils.commands.get_bb_var(); 读取多个变量时使用 oeqa.utils.commands.get_bb_vars()。

  • 调用 BitBake 构建: 使用 oeqa.utils.commands.bitbake()。

  • 运行命令: 使用 oeqa.utils.commands.runCmd()。

oeqa.utils.commands.runqemu() 可启动 runqemu, 以便在运行中的虚拟化镜像内测试。

这些测试可以按测试类并行运行。同一类中的测试应始终使用同一次构建, 不同类或模块则可能分配到不同构建中。 测试通过启动 bitbake 命令工作,位于其上下文之外, 因此没有可直接使用的数据存储,也不能直接使用通常的 BitBake 库函数 bb.\*。

1.5.3 testimage

这类测试在镜像已经启动后运行,目标可以是真实硬件,也可以是 QEMU。 测试假定自身面对目标镜像环境,而不是主机构建环境。 meta/lib/oeqa/runtime/cases/python.py 中有如下简单示例:

class PythonTest(OERuntimeTestCase):
   @OETestDepends(['ssh.SSHTest.test_ssh'])
   @OEHasPackage(['python3-core'])
   def test_python3(self):
      cmd = "python3 -c \\"import codecs; print(codecs.encode('Uryyb, jbeyq', 'rot13'))\""
      status, output = self.target.run(cmd)
      msg = 'Exit status was not 0. Output: %s' % output
      self.assertEqual(status, 0, msg=msg)

这里 OERuntimeTestCase 包装了 unittest.TestCase。 self.target 表示目标系统,测试可以通过它的 run() 方法执行命令。

使用 OETestDepends 和 OEHasPackage 装饰器, 可以确保所需测试依赖或软件包存在。 例如,上述测试只有在镜像安装了 python3-core 时才有意义。

1.5.4 testsdk_ext

这些测试针对已经构建的可扩展 SDK 运行, 可以假定 eSDK 环境已经完成初始化。 meta/lib/oeqa/sdk/cases/devtool.py 中有如下示例:

class DevtoolTest(OESDKExtTestCase):
   @classmethod def setUpClass(cls):
      myapp_src = os.path.join(cls.tc.esdk_files_dir, "myapp")
      cls.myapp_dst = os.path.join(cls.tc.sdk_dir, "myapp")
      shutil.copytree(myapp_src, cls.myapp_dst)
      subprocess.check_output(['git', 'init', '.'], cwd=cls.myapp_dst)
      subprocess.check_output(['git', 'add', '.'], cwd=cls.myapp_dst)
      subprocess.check_output(['git', 'commit', '-m', "'test commit'"], cwd=cls.myapp_dst)

   @classmethod
   def tearDownClass(cls):
      shutil.rmtree(cls.myapp_dst)
   def _test_devtool_build(self, directory):
      self._run('devtool add myapp %s' % directory)
      try:
      self._run('devtool build myapp')
      finally:
      self._run('devtool reset myapp')
   def test_devtool_build_make(self):
      self._test_devtool_build(self.myapp_dst)

此例验证在 eSDK 中,devtool build 是否能够构建一个示例应用。

1.5.5 testsdk

这些测试针对已经构建的 SDK 运行, 可以假定 SDK 已解压且环境脚本已载入。 meta/lib/oeqa/sdk/cases/python.py 中有如下简单示例:

class Python3Test(OESDKTestCase):
   def setUp(self):
         if not (self.tc.hasHostPackage("nativesdk-python3-core") or
               self.tc.hasHostPackage("python3-core-native")):
            raise unittest.SkipTest("No python3 package in the SDK")

   def test_python3(self):
         cmd = "python3 -c \\"import codecs; print(codecs.encode('Uryyb, jbeyq', 'rot13'))\""
         output = self._run(cmd)
         self.assertEqual(output, "Hello, world\n")

如果 SDK 中安装了 nativesdk-python3-core, 测试会调用 python3 解释器运行一条基本命令,检查其是否正常。 只有 SDK 安装了 Python3 时才运行该测试。

1.5.6 oe-build-perf-test

性能测试通常测量操作耗时及过程中的资源使用情况。 以下例子来自 meta/lib/oeqa/buildperf/test_basic.py:

class Test3(BuildPerfTestCase):
   def test3(self):
         """Bitbake parsing (bitbake -p)"""
         # Drop all caches and parse
         self.rm_cache()
         oe.path.remove(os.path.join(self.bb_vars['TMPDIR'], 'cache'), True)
         self.measure_cmd_resources(['bitbake', '-p'], 'parse_1',
                  'bitbake -p (no caches)')
         # Drop tmp/cache
         oe.path.remove(os.path.join(self.bb_vars['TMPDIR'], 'cache'), True)
         self.measure_cmd_resources(['bitbake', '-p'], 'parse_2',
                  'bitbake -p (no tmp/cache)')
         # Parse with fully cached data
         self.measure_cmd_resources(['bitbake', '-p'], 'parse_3',
                  'bitbake -p (cached)')

它在使用或不使用不同缓存的情况下测量三种解析耗时, 用来观察 BitBake 解析性能随时间的变化。

1 编写测试时的注意事项

Autobuilder 上的资源会被多个工作节点并发访问, 因此编写测试时应注意以下约束:

不允许运行 cleanall。

该操作可能删除 DL_DIR 中的文件,破坏其他并行构建。 如果必须执行,需将 DL_DIR 指向隔离目录。

不允许运行 cleansstate。

该操作可能删除 SSTATE_DIR 中的文件,破坏其他并行构建。 如果必须执行,需将 SSTATE_DIR 指向隔离目录。 也可以对 bitbake 使用 -f,通过改变任务的共享状态校验和将任务标记为需要重做, 从而阻止复用对应缓存。

测试不应修改元数据。

这一点对并行运行的 oe-selftest 尤其重要。 修改元数据会改变校验和,干扰同时运行的 BitBake。 确有需要时,应把层复制到临时位置再修改。 例如 devtool 测试确实需要变更元数据,就必须先准备临时副本以保护原数据。