
本文将以bond单元测试为例,演示单元测试的运行、问题排查以及源码分析。大部分源码分析直接写在代码注释里。
该系列文章有4篇:
本文已同步至:
个人博客:itwakeup.com
微信公众号:vpp与dpdk研习社(vpp_dpdk_lab)
命令 | 分类 | 作用 |
|---|---|---|
| 编译运行 | 运行【基础】功能测试(使用默认优化编译) |
| 运行【基础】功能测试(使用Debug 编译) | |
| 运行基础 + 扩展测试(优化编译) | |
| 运行基础 + 扩展测试(Debug 编译) | |
| 重新编译VPP 的 Python API (PAPI) 代码。 | |
| 重新运行失败的测试 | 重新运行上次失败的【基础】测试,对应 |
| 重新运行上次失败的所有测试(基础 + 扩展),对应 | |
| 重新运行上次失败的【基础】测试,对应 | |
| 重新运行上次失败的所有测试(基础 + 扩展),对应 | |
| 代码覆盖率报告 | 生成功能测试的代码覆盖率报告 |
| 生成基础 + 扩展测试的代码覆盖率报告 | |
| 覆盖率测试阶段1:准备 lcov | |
| 覆盖率测试阶段3:生成 lcov HTML 报告 | |
| 清除文件 | 清除测试生成的临时文件 |
| 清除测试框架生成的 代码覆盖率报告 | |
| 全面清理测试生成的所有临时文件和覆盖率报告,相当于 | |
| 进入交互模式 | 进入测试环境交互式 Shell(对应 |
| 进入测试环境交互式 Shell(对应 | |
| 安装依赖的 Python 包 | 刷新测试依赖的 Python 包(如 |
变量 | 说明 |
|---|---|
| 日志级别:0=ERROR, 1=INFO, 2=DEBUG |
| 并行测试进程数, |
| VPP 进程使用的最大 CPU 核数( |
| 首次失败时停止测试(默认 |
| 单测试超时时间(秒),默认600 |
| 失败测试重试次数(默认 |
变量 | 说明 |
|---|---|
| core:VPP 崩溃时自动加载 core dump 到 gdb
+ gdb:在运行测试用例前,打印 VPP 进程 ID(PID)同时暂停执行(等待用户手动输入,如按回车键,才继续运行),此时开发者手动附加 |
| 单步执行测试(手动确认每一步),默认值:no |
| 控制 VPP 崩溃时生成的 core dump 文件大小限制。通过 |
| 当未设置DEBUG时,自动压缩生成的 core dump 文件。默认值:no |
| 打开单元测试框架的调试选项,输出框架更详细的信息,用于开发者排查测试框架的底层问题。 默认值:no |
| 控制是否使用 |
测试过滤使用 TEST 环境变量,支持逗号分隔的多个过滤表达式。
如果不指定 TEST 参数(默认为空字符串),则会运行所有测试。
说明 | |
|---|---|
简单过滤 | 通过文件名或文件后缀匹配:
|
通配符过滤器 | 提供基于测试文件、测试类和测试函数的高级筛选功能:
格式:<文件名>.<测试类>.<测试函数>
示例说明:
|
变量 | 说明 |
|---|---|
| 启用通过 cProfile 模块对测试框架进行性能分析 默认值:no |
| 指定性能分析报告的排序方式。这个选项的值可以参考 Python cProfile 模块文档中支持的排序选项。 常见的排序选项包括: 1. 'calls' - 调用次数 2. 'cumulative' - 累计时间 3. 'time' - 内部时间 |
| 指定性能分析结果的输出文件 默认值:标准输出(stdout) |
变量 | 说明 |
|---|---|
| 加载外部目录的测试脚本(目录下的test_*.py脚本) |
| 加载外部目录里的 VPP 插件 |
| 设置随机种子,如1234,默认使用time.time() |
| 启用 VPP API 模糊测试。 通过向 API 接口发送 随机、异常或非预期的输入数据,检测系统是否存在崩溃、内存泄漏、安全漏洞或逻辑错误。 |
| 选项控制 VPP 测试框架如何处理 VPP 进程的标准输出(stdout)和错误输出(stderr),决定是否缓存并在测试结束后统一输出日志。 默认值:yes |
| 不同 CPU 型号可能有不同的指令集优化,VPP 会针对不同架构生成优化代码。该变量用于指定特定 -march进行单元测试。默认值为空,表示使用通用版本。 注意:需要确保编译时已生成对应架构的优化代码 |
| 用于控制是否在运行测试时执行 sanity 检查(健全性检查)。
意义:
1. 如果 VPP 本身或依赖的 API( |
| 控制是否运行扩展测试,默认值=no |
| 启用专门为代码覆盖率设计的测试,测试一些正常情况下不太可能执行到的代码分支。 default: no |
执行单元测试的默认配置在:test/asf/asfframework.py,见函数def setUpConstants(cls):
def setUpConstants(cls):
...
...
cls.vpp_cmdline = [
config.vpp,
"unix",
"{",
"nodaemon",
debug_cli,
"full-coredump",
coredump_size,
"runtime-dir",
...
]
...例如修改test_xxx.py测试的buffers-per-numa配置,可以使用extra_vpp_config进行修改:
class TestXXX(VppTestCase):
extra_vpp_config = [
"buffers { buffers-per-numa 4096}",
]
...
如果想开启/关闭某个插件,可以使用extra_vpp_plugin_config修改:
class TestXXX(VppTestCase):
...
extra_vpp_plugin_config = [
"plugin dtls_decoder_plugin.so {enable}",
"plugin session_manager_plugin.so {enable}",
]
...修改worker个数可以直接修改vpp_worker_count:
class TestXXX(VppTestCase):
...
vpp_worker_count = 4
...1)编译vpp
首先确保vpp可编译,运行。如果make test,则需要先make rebuild-release,make test-debug则需要make build,运行单元测试时vpp不需要运行。
2)安装python依赖
执行test-refresh-deps,将在python虚拟环境中安装依赖。
如果提示库冲突,可以先把宿主机里相关的python库卸载。
3)在debug版本下运行所有单元测试
# 编译运行
make test-debug
# 重新运行失败的单元测试
make retest-debug4)debug版本下运行指定测试文件:
make test-debug TEST=handoffmake test-debug TEST=handoff
5)运行测试用例时使用gdb调试vpp
# 测试handoff,同时gdb attach
make retest-debug TEST=handoff DEBUG=gdb根据提示,测试测试用例暂停,等待你执行gdb命令后,再按任意键继续
Running tests in foreground in the current process
==============================================================================
Spawned VPP with PID: 1116506
------------------------------------------------------------------------------
You can debug VPP using:
sudo gdb /data/code/vpp/build-root/install-vpp_debug-native/vpp/bin/vpp -ex 'attach 1116506'
Now is the time to attach gdb by running the above command and set up breakpoints etc., then resume VPP from within gdb by issuing the 'continue' command
------------------------------------------------------------------------------
Press ENTER to continue running the testcase...执行gdb追加pid
gdb -p 1116506
(gdb) c
Continuing.回到执行测试用例的终端,按任意键继续。
6)错误问题定位
以下是执行单元测试错误的一个例子:
Handoff test [4 worker threads]
==============================================================================
Handoff test [CLI] 17.44 ERROR [ temp dir used by test case: /tmp/vpp-unittest-TestHandoff ]
Handoff test [CLI] 14.97 ERROR [ temp dir used by test case: /tmp/vpp-unittest-TestHandoff ]
test_handoff_symmetrical_l4_innermost_from_pcaps (test_handoff.TestHandoff) 16.19 ERROR [ temp dir used by test case: /tmp/vpp-unittest-TestHandoff ]
test_handoff_symmetrical_l4_outermost_from_pcaps (test_handoff.TestHandoff) 14.38 ERROR [ temp dir used by test case: /tmp/vpp-unittest-TestHandoff ]
# ls /tmp/vpp-unittest-TestHandoff -l
total 556
srwxrwxr-x. 1 root root 0 May 22 10:11 api.sock
-rw-r--r--. 1 root root 183287 May 22 10:21 log.txt
-rw-r--r--. 1 root root 8356 May 22 10:20 pcap-decode-suiteTestHandoff.txt
srwxrwxr-x. 1 root root 0 May 22 10:11 snort.sock
srwxrwxr-x. 1 root root 0 May 22 10:11 stats.sock
-rw-r--r--. 1 root root 82 May 22 10:20 'suiteTestHandoff.[timestamp:1747880421.73528147].pg0-inp-0000.pg0_in.pcap'
-rw-r--r--. 1 root root 82 May 22 10:20 'suiteTestHandoff.[timestamp:1747880421.77663755].pg0-out-0000.pg0_out.pcap'
-rw-r--r--. 1 root root 82 May 22 10:20 'suiteTestHandoff.[timestamp:1747880421.78589582].pg1-inp-0000.pg1_in.pcap'
-rw-r--r--. 1 root root 82 May 22 10:20 'suiteTestHandoff.[timestamp:1747880421.81018996].pg1-out-0000.pg1_out.pcap'
-rw-r--r--. 1 root root 85414 May 22 10:20 vpp_api_trace.test_handoff_cli.1116506.log
-rw-r--r--. 1 root root 86036 May 22 10:20 vpp_api_trace.test_handoff_symmetrical_cli.1116506.log
-rw-r--r--. 1 root root 86691 May 22 10:21 vpp_api_trace.test_handoff_symmetrical_l4_innermost_from_pcaps.1116506.log
-rw-r--r--. 1 root root 87346 May 22 10:21 vpp_api_trace.test_handoff_symmetrical_l4_outermost_from_pcaps.1116506.log根据提示,可以看到错误日志等信息存放在/tmp/vpp-unittest-TestHandoff目录。
termshark -r来读取分析。可以先执行一下make test-refresh-deps,刷新一下python环境里的依赖库。
需要注意的是,如果你在虚拟环境外,使用root安装了依赖python依赖,那么虚拟环境将沿用外部的python库。
我们进入虚拟环境查看scapy版本,这是由于在外部环境使用root安装饿了scapy,导致虚拟空间沿用了外部的python库:
root@localhost vpp# source build-root/test/venv/bin/activate
(venv) root@localhost venv (modify-name) # python -m scapy
WARNING: No alternative Python interpreters found ! Using standard Python shell instead.
INFO: Using the default Python shell: History is disabled.
aSPY//YASa
apyyyyCY//////////YCa |
sY//////YSpcs scpCY//Pp | Welcome to Scapy
ayp ayyyyyyySCP//Pp syY//C | Version 2.6.1 查看vpp写的依赖版本是2.4.3:
root@localhost vpp# cat ./test/requirements.txt
...
scapy==2.4.3; python_version >= '2.7' or python_version >= '3.4'
...但是无论是2.4.3还是2.6.1都有问题,尝试了以下几个版本:
1)scapy>=2.4.5版本,ERSPAN移了位置。
ImportError: cannot import name 'ERSPAN' from 'scapy.layers.l2' (/usr/local/lib/python3.9/site-packages/scapy/layers/l2.py)
Killing possible remaining process IDs: 1020374 1020376
No symlinks to failed tests' temporary directories found in /tmp/vpp-failed-unittests/.
make[1]: *** [Makefile:306: test] Error 1
make[1]: Leaving directory '/data/code/vpp/test'
make: *** [Makefile:505: test-debug] Error 22)scapy>=2.5.0版本,对于duplicate parameter报错。
File "<frozen importlib._bootstrap_external>", line 850, in exec_module
File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
File "/data/code/vpp/test/test_bfd.py", line 23, in <module>
from bfd import (
File "/data/code/vpp/test/bfd.py", line 114, in <module>
class BFD(Packet):
File "/usr/local/lib/python3.9/site-packages/scapy/base_classes.py", line 339, in __new__
dct["__signature__"] = inspect.Signature([
File "/usr/lib64/python3.9/inspect.py", line 2826, in __init__
raise ValueError(msg)
ValueError: duplicate parameter name: 'auth_key_hash'3)scapy<2.4.5版本,find_library("libc"),在linux下应该写find_library("c")
File "/usr/local/lib/python3.9/site-packages/scapy/arch/__init__.py", line 27, in <module>
from scapy.arch.bpf.core import get_if_raw_addr
File "/usr/local/lib/python3.9/site-packages/scapy/arch/bpf/core.py", line 30, in <module>
LIBC = cdll.LoadLibrary(find_library("libc"))
File "/usr/lib64/python3.9/ctypes/util.py", line 330, in find_library
_get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
File "/usr/lib64/python3.9/ctypes/util.py", line 147, in _findLib_gcc
if not _is_elf(file):
File "/usr/lib64/python3.9/ctypes/util.py", line 99, in _is_elf
with open(filename, 'br') as thefile:
FileNotFoundError: [Errno 2] No such file or directory: b'liblibc.a'
root@localhost vpp (dev-5.0) # cat /usr/local/lib/python3.9/site-packages/scapy/arch/bpf/core.py|grep lib
from ctypes.util import find_library
LIBC = cdll.LoadLibrary(find_library("libc"))综上,貌似没有一个完美的scapy版本,最后scapy选择使用2.4.5版本。
1)修改./test/requirements.txt把scapy改为2.4.5
2)修改test/test_span.py,兼容新旧版本的ERSPAN路径
# 修改这行代码
# from scapy.layers.l2 import Ether, GRE, ERSPAN
from scapy.layers.l2 import Ether, GRE
try:
from scapy.layers.l2 import ERSPAN
except ImportError:
from scapy.contrib.erspan import ERSPAN3)由于我使用的是root账号,为了避免与虚拟环境冲突,重新安装scapy版本为2.4.5
pip3 install scapy==2.4.54)卸载其他与虚拟环境冲突的python依赖(根据make test-refresh-deps的报错情况执行)
我的环境需要:
pip3 uninstall weasel
pip3 uninstall pydantic
pip3 uninstall typing-extensions现在重新执行make retest-debug,单元测试已经可以跑起来了。
==============================================================================
Allow/Deny Plugin Unit Test Cases [main thread only]
==============================================================================
Plugin API Test 6.47 OK
==============================================================================
API Internal client Test Cases [main thread only]
==============================================================================
Internal API client 3.56 OK
==============================================================================
JSON API trace related tests [main thread only]
...root@localhost vpp # make test-help
make[1]: Entering directory '/data/code/vpp/test'
Running tests:
test - build and run (basic) functional tests
test-debug - build and run (basic) functional tests (debug build)
test-cov - generate code coverage report for functional tests
test-cov-prep - coverage phase #1 : prepare lcov
test-cov-build - coverage phase #2 : build gcov image & run tests against it (use TEST=)
test-cov-post - coverage phase #3 : generate lcov html report
test-cov-both - generate and merge code coverage report for Python and Golang tests
test-all - build and run functional and extended tests
test-all-debug - build and run functional and extended tests (debug build)
test-all-cov - generate code coverage report for functional and extended tests
retest - run functional tests
retest-debug - run functional tests (debug build)
retest-all - run functional and extended tests
retest-all-debug - run functional and extended tests (debug build)
test-wipe - wipe (temporary) files generated by unit tests
test-wipe-cov - wipe code coverage report for test framework
test-wipe-papi - rebuild vpp_papi sources
test-wipe-all - wipe (temporary) files generated by unit tests, and coverage
test-shell - enter shell with test environment
test-shell-debug - enter shell with test environment (debug build)
test-refresh-deps - refresh the Python dependencies for the tests
Environment variables controlling test runs:
V=[0|1|2]
set test verbosity level: 0=ERROR, 1=INFO, 2=DEBUG
TEST_JOBS=[<n>|auto]
use at most <n> parallel python processes for test
execution, if auto, set to number of available cpus
(default: 1)
MAX_VPP_CPUS=[<n>|auto]
use at most <n> cpus for running vpp
'auto' sets to number of available cpus
(default: auto)
CACHE_OUTPUT=[0|n|no]
disable caching VPP stdout/stderr and logging it
as one block after test finishes
(default: yes)
FAILFAST=[1|y|yes]
if enabled, stop running tests on first failure
otherwise finish running whole suite
(default: no)
TIMEOUT=<timeout>
fail test suite if any single test takes longer
than <timeout> (in seconds) to finish
(default: 600)
RETRIES=<n>
retry failed tests <n> times
(default: 0)
DEBUG=<type>
configure VPP debugging:
DEBUG=core
detect coredump and load it in gdb on crash
DEBUG=gdb
print VPP PID and wait for user input before
running and tearing down a testcase, allowing
easy gdb attach
DEBUG=gdbserver
same as above, but run gdb inside a gdb server
DEBUG=attach
attach to existing vpp in running in gdb
(see test-start-vpp-in-gdb)
(default: none)
STEP=[1|y|yes]
enable stepping through a testcase
(default: no)
SANITY=[0|n|no]
disable sanity import of vpp-api/vpp sanity
run before running tests
(default: yes)
EXTENDED_TESTS=[1|y|yes]
run extended tests
(default: no)
TEST=<filter>,[<filter>],...
only run tests matching one or more comma-delimited
filter expressions
simple filter:
file name or file suffix select all tests from a file
examples:
TEST=test_bfd
TEST=bfd
equivalent expressions selecting all
tests defined in test_bfd.py
wildcard filter:
advanced filtering based on test file, test class
and test function
each filter expression is in the form of
<file>.<class>.<test function>
each of the tokens can be left empty or replaced
with '*' to select all objects available
examples:
TEST=test_bfd.*.*
TEST=test_bfd..
TEST=bfd.*.*
TEST=bfd..
select all tests defined in test_bfd.py
TEST=bfd.BFDAPITestCase.*
TEST=bfd.BFDAPITestCase.
select all tests from test_bfd.py
which are part of BFDAPITestCase class
TEST=bfd.BFDAPITestCase.test_add_bfd
select a single test named test_add_bfd
from test_bfd.py/BFDAPITestCase
TEST=..test_add_bfd
TEST=*.*.test_add_bfd
select all test functions named test_add_bfd
from all files/classes
TEST=bfd,ip4,..test_icmp_error
select all test functions in test_bfd.py,
test_ip4.py and all test functions named
'test_icmp_error' in all files
(default: '')
VARIANT=<variant>
specify which march node variant to unit test
e.g. VARIANT=skx test the skx march variants
e.g. VARIANT=icl test the icl march variants
(default: '')
COREDUMP_SIZE=<size>
pass <size> as unix { coredump-size <size> } argument
to vpp, e.g. COREDUMP_SIZE=4g or COREDUMP_SIZE=unlimited
(default: '')
COREDUMP_COMPRESS=[1|y|yes]
if no debug option is set, compress any core files
(default: no)
EXTERN_TESTS=<path>
include out-of-tree test_*.py files under <path>
(default: '')
EXTERN_PLUGINS=<path>
load out-of-tree vpp plugins in <path>
(default: '')
EXTERN_COV_DIR=<path>
path to out-of-tree prefix, where source, object
and .gcda files can be found for coverage report
(default: '')
PROFILE=[1|y|yes]
enable profiling of test framework via cProfile module
(default: no)
PROFILE_SORT_BY=opt
sort profiling report by opt - see cProfile documentation
for possible values
(default: cumtime)
PROFILE_OUTPUT=file
output profiling info to file - use absolute path
(default: stdout)
TEST_DEBUG=[1|y|yes]
enable debugging of the test framework itself (expert)
(default: no)
TEST_GCOV=[1|y|yes]
enable tests specifically designed soley for code coverage
(default: no)
API_FUZZ=[1|y|yes]
enable VPP api fuzz testing
(default: no)
RND_SEED=<seed>
random seed used by test framework
(default: time.time())
DECODE_PCAPS=[all|failed|none]
decode pcap files using tshark - all, only failed or none
(default: failed)
Starting VPP in GDB for use with DEBUG=attach:
test-start-vpp-in-gdb - start VPP in gdb (release)
test-start-vpp-debug-in-gdb - start VPP in gdb (debug)原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。