4 执行自动化运行时测试
OpenEmbedded 构建系统为镜像提供了一系列自动化测试,用于验证运行时功能。
这些测试可以在 QEMU 或实际目标硬件上运行。测试使用 Python 的 unittest
模块编写,大多数会通过 SSH 在目标系统上执行命令。本节介绍如何配置测试环境、
运行现有测试,以及编写和添加自己的测试。
关于 Yocto 项目提供的测试和质量保证基础设施,参见 Yocto 项目参考手册的 测试与质量保证 一节。
4 启用测试
根据计划使用 QEMU 还是实际硬件运行测试,启用步骤有所不同。 以下各小节分别介绍这两种测试方式。
4 在 QEMU 上启用运行时测试
运行测试前,需要完成以下工作:
配置网络操作,避免 sudo 交互: 可以选择以下方式之一:
在
/etc/sudoers中为用户添加NOPASSWD,作用于所有命令, 或仅作用于runqemu-ifup。必须指定完整路径,因为使用多个源码仓库克隆时, 路径可能不同。备注
某些发行版还需要注释掉
/etc/sudoers中的 "Defaults requiretty"。手动为系统配置 tap 接口。
以 root 身份运行
scripts/runqemu-gen-tapdevs脚本,生成一组 tap 设备。 Autobuilder 一类环境通常采用这种方式。备注
通过 sudo 调用此脚本时,务必使用绝对路径。
确保主机安装了
iptables软件包。运行该脚本需要
qemu-helper-native软件包配方。 使用以下命令构建该软件包::$ bitbake qemu-helper-native
设置 DISPLAY 变量: 需要设置该变量,以便使用 X 服务器 (例如,为无显示器的机器启动
vncserver)。确保主机防火墙接受来自 192.168.7.0/24 的连接: 某些测试,尤其是 DNF 测试,会在随机的高端口上启动 HTTP 服务器, 向目标设备提供文件。DNF 模块提供
${WORKDIR}/oe-rootfs-repo的访问, 以便运行 DNF 软件源命令。因此,主机防火墙必须接受来自 192.168.7.0/24 的连接,这是runqemu为 tap 设备使用的默认 IP 地址范围。确保主机安装了正确的软件包: 根据主机发行版,需要安装:
Ubuntu 和 Debian:
sysstat和iproute2openSUSE:
sysstat和iproute2Fedora:
sysstat和iprouteCentOS:
sysstat和iproute
开始运行测试后,会依次执行以下操作:
将根文件系统的副本写入
${WORKDIR}/testimage。使用标准
runqemu脚本在 QEMU 中启动镜像。默认等待最多 500 秒,使启动过程进入登录提示符。 可以在 configuration file 中设置 TEST_QEMUBOOT_TIMEOUT 修改超时时间。
启动完成并出现登录提示符后,开始运行测试。 完整启动日志写入
${WORKDIR}/testimage/qemu_boot_log。按 TEST_SUITES 中的顺序加载各测试模块。 通过 SSH 执行的命令的完整输出位于
${WORKDIR}/testimgage/ssh_target_log。如果没有失败,测试任务成功结束。
unittest的输出位于任务日志${WORKDIR}/temp/log.do_testimage。
4 在硬件上启用运行时测试
OpenEmbedded 构建系统可以在真实硬件上运行测试;对于某些设备, 还可以在测试前将待测镜像部署到设备上。
为了自动部署,初始设置时需要在硬件上安装一次“控制器镜像”。 此后,每次运行测试时执行以下操作:
启动控制器镜像,并用它将待测镜像写入第二个分区。
使用你提供的外部脚本重新启动设备。
设备启动进入待测镜像。
运行测试时,无论镜像是否自动部署,设备都应以预先确定的 IP 地址接入网络。 可以在镜像中写入静态 IP,也可以让镜像使用 DHCP, 并由测试网络中的 DHCP 服务器根据设备 MAC 地址分配已知 IP。
要在硬件上运行测试,需要为 TEST_TARGET 设置适当的值。 对于 QEMU,无需修改,默认值就是 "qemu"。硬件测试可使用以下选项:
"simpleremote": 如果目标系统已经运行待测镜像且可以通过网络访问, 选择 "simpleremote"。它既可用于真实硬件,也可用于单独启动的 QEMU 或其他虚拟机管理器中运行的镜像。
"SystemdbootTarget": 如果硬件是基于 EFI 的机器,使用
systemd-boot引导加载程序,并已安装core-image-testmaster或类似镜像, 则选择 "SystemdbootTarget"。待测硬件还必须接入启用了 DHCP 的网络, 并在每次重启后获得相同的 IP 地址。该选项还涉及其他要求和注意事项,详见后面的 选择 SystemdbootTarget 一节。
"BeagleBoneTarget": 如果在 BeagleBone "Black" 或最初的 "White" 硬件上部署镜像并运行测试,选择 "BeagleBoneTarget"。使用方式见
meta-yocto-bsp/lib/oeqa/controllers/beaglebonetarget.py文件顶部的注释。"GrubTarget": 如果在使用 GRUB 引导的通用 PC 上部署镜像并运行测试, 选择 "GrubTarget"。使用方式见
meta-yocto-bsp/lib/oeqa/controllers/grubtarget.py文件顶部的注释。"your-target": 如果要在 BSP 层中的自定义机器上部署镜像并测试, 可以创建自定义目标。需要在层的
lib/oeqa/controllers/下添加 定义目标类的 Python 模块,并提供空的__init__.py。 示例见meta-yocto-bsp/lib/oeqa/controllers/中的文件。
4 选择 SystemdbootTarget
如果没有将 TEST_TARGET 设为 "SystemdbootTarget", 可以跳过本节,直接阅读 运行测试。
如果设置了该值,还需要按以下步骤对控制器镜像进行一次性配置:
设置 EFI_PROVIDER: 确保 EFI_PROVIDER 如下::
EFI_PROVIDER = "systemd-boot"
构建控制器镜像: 构建
core-image-testmaster镜像。 此配方是“控制器”镜像的示例,可以像其他配方一样进行定制。镜像配方必须满足以下要求:
继承
core-image,以便安装内核模块。安装常规 Linux 工具,而非 BusyBox 版本, 例如
bash、coreutils、tar、gzip和kmod。使用带自定义安装程序的 Initramfs 镜像。 普通可安装镜像通常只创建一个根文件系统分区,而这里使用的安装程序 会创建特定分区布局。并非所有板级支持包(BSP)都能使用安装程序。 在这些情况下,需要在目标上手动创建以下布局:
第一个分区挂载于
/boot,标签为 "boot"。安装此镜像的主根文件系统分区,挂载于
/。另一个标签为 "testrootfs" 的分区,用于部署待测镜像。
安装镜像: 将刚构建的镜像安装到目标系统上。
将 TEST_TARGET 设为 "SystemdbootTarget" 后,最后还需要设置待测镜像:
配置 local.conf: 确保 build/conf/local.conf 中包含以下语句::
IMAGE_FSTYPES += "tar.gz" IMAGE_CLASSES += "testimage" TEST_TARGET = "SystemdbootTarget" TEST_TARGET_IP = "192.168.2.3"
构建待测镜像: 使用 BitBake 构建镜像::
$ bitbake core-image-sato
4 电源控制
除 "simpleremote" 外,大多数硬件目标支持电源控制:
可以组合使用 TEST_POWERCONTROL_CMD 和 TEST_POWERCONTROL_EXTRA_ARGS,定义在主机上运行的电源控制命令。 测试代码向该命令传入一个参数:off、on 或 cycle(先关再开)。 以下示例可以放入 build/conf/local.conf::
TEST_POWERCONTROL_CMD = "powercontrol.exp test 10.11.12.1 nuc1"
在此示例中,expect 脚本执行以下操作:
ssh test@10.11.12.1 "pyctl nuc1 arg"
随后运行一个 Python 脚本,控制标签为
nuc1的设备的电源。备注
需要根据实际环境定制 TEST_POWERCONTROL_CMD 和 TEST_POWERCONTROL_EXTRA_ARGS。唯一要求是命令接受 "on"、"off" 和 "cycle" 作为最后一个参数。
如果未定义电源控制命令,则通过 SSH 连接设备,使用常规 reboot 命令重启。 只要机器确实能够重启(即 SSH 测试没有失败),这种方式就可用。 它适用于配置简单、通常只有一块开发板,并能接受偶尔手动干预的场景。
如果没有能够自动控制电源的硬件,但仍想尝试自动化硬件测试,
可以使用 dialog-power-control 脚本,通过对话框提示你执行所需的电源操作。
该脚本需要安装 KDialog 或 Zenity。使用时设置 TEST_POWERCONTROL_CMD::
TEST_POWERCONTROL_CMD = "${COREBASE}/scripts/contrib/dialog-power-control"
4 串口控制台连接
某些测试目标类需要通过串口控制台与引导加载程序交互, 例如 BeagleBoneTarget 和 GrubTarget。 此时需要通过 TEST_SERIALCONTROL_CMD 指定连接目标串口控制台的命令, 还可以选择设置 TEST_SERIALCONTROL_EXTRA_ARGS。
如果设备连接到本地串口,可以使用串口终端程序;
如果通过远程控制台服务器访问,则可以使用 telnet 或 ssh。
无论哪种方式,该命令只需像普通终端程序一样连接串口控制台,
并将连接转发到标准输入和输出。例如,使用 picocom 以 115200 bps
连接 /dev/ttyUSB0 时,可设置::
TEST_SERIALCONTROL_CMD = "picocom /dev/ttyUSB0 -b 115200"
对于重启时串口设备会消失的本地设备,另有 "serdevtry" 包装脚本可用。
只需在终端命令前加上 ${COREBASE}/scripts/contrib/serdevtry::
TEST_SERIALCONTROL_CMD = "${COREBASE}/scripts/contrib/serdevtry picocom -b 115200 /dev/ttyUSB0"
4 运行测试
可以自动或手动启动测试:
自动运行测试: 要在 OpenEmbedded 构建系统成功创建镜像后自动测试, 先在 Build Directory 的 build/conf/local.conf 中将 TESTIMAGE_AUTO 设为 "1"::
TESTIMAGE_AUTO = "1"
然后构建镜像。如果构建成功,就会运行测试::
bitbake core-image-sato
手动运行测试: 先编辑 build/conf/local.conf, 全局继承 testimage 类::
IMAGE_CLASSES += "testimage"
然后使用 BitBake 运行测试::
bitbake -c testimage image
所有测试文件位于 OpenEmbedded-Core (OE-Core) 的
meta/lib/oeqa/runtime/cases 中。测试名称直接对应 Python 模块,
每个模块可以包含多个独立测试。通常按测试领域分组,
例如 systemd 测试位于 meta/lib/oeqa/runtime/cases/systemd.py。
可以向任何层添加测试,只要将其放在正确位置,
并照常在 build/conf/local.conf 中扩展 BBPATH。
测试必须放在 layer/lib/oeqa/runtime/cases 中。
备注
模块名称不得与 meta/lib/oeqa/runtime/cases 中默认测试模块的名称冲突。
在 build/conf/local.conf 中追加或覆盖 TEST_SUITES,
可以改变要运行的测试集合。其中每个名称都代表该镜像必须运行的测试。
显式列出的模块不能跳过,即使测试不适用于该镜像
(例如,对未安装 rpm 的镜像运行 RPM 测试)。
将 "auto" 追加到 TEST_SUITES 后,构建系统会尝试运行所有适用测试,
此时各模块可以自行决定跳过。
TEST_SUITES 中的顺序很重要,会影响测试依赖。
有依赖的测试应放在其依赖项之后。例如,ssh 测试依赖 ping 测试,
因此列表中 "ssh" 必须位于 "ping" 之后。测试类不负责重新排序或处理依赖。
备注
每个模块可以包含多个类,每个类可以包含多个测试方法,均遵循 Python unittest 规则。
运行测试时应注意:
镜像的默认测试定义如下::
DEFAULT_TEST_SUITES:pn-image = "ping ssh df connman syslog xorg scp vnc date rpm dnf dmesg"
使用以下方式向列表添加自己的测试::
TEST_SUITES:append = " mytest"
按以下方式运行指定测试列表::
TEST_SUITES = "test1 test2 test3"
请记住顺序很重要:依赖其他测试的测试必须放在其后。
4 导出测试
可以导出测试,使其脱离构建系统独立运行。如果需要将测试交给调度器执行, 就必须导出测试。只能导出 TEST_SUITES 中定义的测试。
如果镜像已经构建完成,确保 build/conf/local.conf 中有以下设置::
IMAGE_CLASSES += "testexport"
TEST_TARGET_IP = "IP-address-for-the-test-target"
TEST_SERVER_IP = "IP-address-for-the-test-server"
然后使用以下形式的 BitBake 命令导出测试::
$ bitbake image -c testexport
导出的测试位于 Build Directory 的 tmp/testimage/image 中,
该位置由 TEST_EXPORT_DIR 控制。
现在可以在构建环境之外运行测试::
$ cd tmp/testimage/image
$ ./oe-test runtime
备注
运行测试前,可能需要先在 QEMU 中运行镜像,或将镜像部署到硬件。
下面是一个使用 core-image-sato 镜像并指定 IP 地址的完整示例::
IMAGE_CLASSES += "testexport"
TEST_TARGET_IP = "192.168.7.2"
TEST_SERVER_IP = "192.168.7.1"
使用 BitBake 导出测试::
$ bitbake core-image-sato -c testexport
在构建环境之外,按以下方式运行测试::
$ cd tmp/testimage/core-image-sato
$ ./oe-test runtime
4 编写新测试
如前所述,新测试文件必须位于正确位置,构建系统才能找到它们。
对于核心之外的额外功能,测试应添加到提供该功能的层中,
路径为 layer/lib/oeqa/runtime/cases,并照常在该层的 layer.conf
中扩展 BBPATH。需要注意:
文件名必须直接对应测试模块名称。
模块名称不得与现有核心测试冲突。
runtime 目录下至少需要存在空的
__init__.py文件。
创建测试时,可以先复制一个现有模块,例如 oe_syslog.py 或 gcc.py。
测试模块可以使用 meta/lib/oeqa/utils 中的辅助类。
备注
组织 shell 命令时,应保证其行为可靠,并用单一返回码表示成功。
有时还需要解析输出,示例见 df.py 和 date.py 模块。
所有测试类都继承 meta/lib/oetest.py 中的 oeRuntimeTest。
此基类提供的辅助属性和方法将在以下小节说明。
4 类方法
类方法如下:
hasPackage(pkg): 如果
pkg位于镜像已安装软件包列表中,返回 "True"。 该列表基于 do_rootfs 任务生成的清单文件。hasFeature(feature): 如果功能存在于 IMAGE_FEATURES 或 DISTRO_FEATURES 中,返回 "True"。
4 类属性
类属性如下:
pscmd: 如果镜像安装了
procps,值为 "ps -ef"; 否则为 "ps"(busybox)。tc: 测试上下文,可用于访问以下属性:
d: BitBake 数据存储,可进行
oeRuntimeTest.tc.d.getVar("VIRTUAL-RUNTIME_init_manager")等操作。testslist 和 testsrequired: 内部使用,测试无需使用。
filesdir:
meta/lib/oeqa/runtime/files的绝对路径。 此目录包含要复制到目标上的测试辅助文件,例如用于编译的小型 C 文件。target: 目标控制器对象,用于在特定目标(如 Qemu、SimpleRemote 和 SystemdbootTarget)上部署并启动镜像。测试通常使用以下成员:
ip: 目标 IP 地址。
server_ip: 主机 IP 地址,通常由 DNF 测试套件使用。
run(cmd, timeout=None): 最常用的方法,是
ssh root@host "cmd"的包装。返回二元组 (status, output), 分别表示 "cmd" 的返回码和输出。可选参数 timeout 表示等待命令返回的秒数。 若为 "None",使用实例默认超时时间 300 秒;若为 "0",则一直等待命令返回。copy_to(localpath, remotepath):
scp localpath root@ip:remotepath。copy_from(remotepath, localpath):
scp root@host:remotepath localpath。
4 实例属性
唯一的实例属性是 target,与上一节所述的同名类属性相同。
同时提供实例属性和类属性,是为了使测试能在实例方法中使用
self.target.run(cmd),而不必使用 oeRuntimeTest.tc.target.run(cmd)。
4 在不使用包管理器的情况下向待测设备安装软件包
如果测试需要由 BitBake 构建的软件包,可以将其安装到待测设备(DUT),
而不要求设备安装包管理器。但必须具备 SSH 连接,且目标使用 sshcontrol 类。
备注
此方法使用 scp 将文件从主机复制到目标,因此会丢失权限及特殊属性。
通过 JSON 文件定义测试需要的软件包。文件必须与测试定义文件位于同一路径,
名称直接对应测试模块名称,扩展名为 .json。
JSON 文件中的对象以测试名称为键,其值为对象或数组。 该对象或对象数组使用以下字段:
"pkg":必需的字符串,指定要安装的软件包名称。
"rm":可选布尔值,默认为 "false",指定是否在测试后删除软件包。
"extract":可选布尔值,默认为 "false",指定是否需要从软件包格式中提取内容。 设置为 "true" 时,不会自动将软件包安装到待测设备。
以下 JSON 示例让测试 "foo" 安装软件包 "bar", 让测试 "foobar" 安装软件包 "foo" 和 "bar"。 测试完成后,从待测设备删除这些软件包::
{
"foo": {
"pkg": "bar"
},
"foobar": [
{
"pkg": "foo",
"rm": true
},
{
"pkg": "bar",
"rm": true
}
]
}