diff --git a/config/app-blocklist.toml b/config/app-blocklist.toml new file mode 100644 index 000000000..19be09579 --- /dev/null +++ b/config/app-blocklist.toml @@ -0,0 +1,67 @@ +# ========================================================= +# DADK 应用程序黑名单配置文件模板 +# ========================================================= +# +# 应用程序黑名单功能允许用户指定不希望编译和安装的应用程序。 +# 当黑名单中的应用程序被检测到时,DADK 会根据配置自动跳过这些应用程序的构建和安装过程。 + +# ========================================================= +# 全局配置选项 +# ========================================================= + +# 是否启用严格模式(可选) +# - true(默认):严格模式,跳过被屏蔽的应用程序并记录警告 +# - false:非严格模式,只记录警告但不跳过应用程序 +strict = true + +# 是否在日志中显示被跳过的应用(可选) +# - true(默认):在日志中显示被跳过的应用程序信息 +# - false:静默模式,不显示被跳过的应用程序 +log_skipped = true + +# ========================================================= +# 被屏蔽的应用程序列表 +# ========================================================= +# +# 支持以下匹配方式: +# 1. 精确匹配: name = "app1" +# 2. 版本匹配: name = "openssl@1.1.1" +# 3. 通配符名称: name = "test-*" +# 4. 通配符版本: name = "nginx@1.*" +# 5. 复合模式: name = "lib*@2.*" +# +# 每个应用可以选择性地提供屏蔽原因(reason) + + +# ========================================================= +# 配置说明和注意事项 +# ========================================================= +# +# 1. **依赖关系**:如果其他应用程序依赖被屏蔽的应用程序,构建过程可能会失败。 +# 请确保处理好依赖关系。 +# +# 2. **模式匹配优先级**: +# - 精确匹配 > 版本匹配 > 模式匹配 +# - 如果有多个模式都匹配,使用第一个匹配的结果 +# +# 3. **通配符语法**: +# - "*" 匹配任意数量的字符(包括0个) +# - "?" 匹配单个字符 +# - 支持在名称和版本中使用通配符 +# +# 4. **版本格式**: +# - 版本号使用 "@" 符号分隔,如 "app@1.0.0" +# - 版本号支持通配符,如 "app@1.*" +# +# 5. **配置文件路径**: +# - 默认路径:config/app-blocklist.toml +# - 可在 dadk-manifest.toml 中通过 app-blocklist-config 字段自定义路径 +# +# 6. **字段说明**: +# - name:应用程序名称或匹配模式(必需) +# - reason:屏蔽原因说明(可选,建议提供以便调试和维护) + +# 屏蔽gvisor系统调用测试 +[[blocked_apps]] +name = "gvisor syscall tests" +reason = "由于文件较大,因此屏蔽。如果要允许系统调用测试,则把这几行取消注释即可" diff --git a/config/rootfs.toml b/config/rootfs.toml index 03ff2c9f0..2a8289522 100644 --- a/config/rootfs.toml +++ b/config/rootfs.toml @@ -2,7 +2,7 @@ # Filesystem type (options: `fat32`) fs_type = "fat32" # Size of the rootfs disk image (eg, `1G`, `1024M`) -size = "1G" +size = "2G" [partition] # Partition type (options: "none", "mbr", "gpt") diff --git a/dadk-manifest.toml b/dadk-manifest.toml index bafabac49..b3ba968e6 100644 --- a/dadk-manifest.toml +++ b/dadk-manifest.toml @@ -22,3 +22,7 @@ cache-root-dir = "bin/dadk_cache" # User configuration directory path # 这个字段只是临时用于兼容旧版本,v0.2版本重构完成后会删除 user-config-dir = "user/dadk/config" + +# Application blocklist configuration file path +app-blocklist-config = "config/app-blocklist.toml" + diff --git a/docs/kernel/ktest/gvisor_syscall_test.rst b/docs/kernel/ktest/gvisor_syscall_test.rst new file mode 100644 index 000000000..ff3cb9ea4 --- /dev/null +++ b/docs/kernel/ktest/gvisor_syscall_test.rst @@ -0,0 +1,79 @@ +============================== +gVisor 系统调用测试 +============================== + +DragonOS 集成了 gVisor 系统调用测试套件,用于验证操作系统系统调用实现的兼容性和正确性。 + +概述 +======== + +gVisor 是 Google 开发的容器运行时沙箱,包含了大量的系统调用兼容性测试。这些测试用于验证操作系统的系统调用实现是否符合 Linux 标准。 + +主要特性: + +- **全面的测试覆盖**:包含数百个系统调用测试用例 +- **白名单机制**:默认只运行已验证的测试,逐步完善支持 +- **黑名单过滤**:可针对每个测试程序屏蔽特定的测试用例 +- **自动化运行**:提供 Makefile 和脚本简化测试流程 + +快速开始 +========== + +1. 进入测试目录: + + .. code-block:: bash + + cd user/apps/tests/syscall/gvisor + +2. 在Linux运行白名单测试(自动下载测试套件): + + .. code-block:: bash + + make test + +3. 如果需要运行测试,请先修改配置文件: + + 编辑 `config/app-blocklist.toml`,注释掉以下内容: + + .. code-block:: toml + + # 屏蔽gvisor系统调用测试 + # [[blocked_apps]] + # name = "gvisor syscall tests" + # reason = "由于文件较大,因此屏蔽。如果要允许系统调用测试,则把这几行取消注释即可" + +4. 在 DragonOS 系统内运行测试: + + 进入安装目录并运行测试程序: + + .. code-block:: bash + + cd /opt/tests/gvisor + ./gvisor-test-runner --help + + 使用 ``./gvisor-test-runner`` 可以运行具体的测试用例。 + +5. 查看详细文档: + + 请参阅 `user/apps/tests/syscall/gvisor/README.md` 获取完整的使用说明。 + +测试机制 +========== + +白名单模式 +----------- + +测试框架默认启用白名单模式,只运行 ``whitelist.txt`` 中指定的测试程序。这允许逐步验证 DragonOS 的系统调用实现。 + +黑名单过滤 +----------- + +对于每个测试程序,可以通过 ``blocklists/`` 目录下的文件屏蔽特定的测试用例。这对于跳过暂不支持或不稳定的测试非常有用。 + +更多详细信息 +============== + +关于 gVisor 系统调用测试的详细使用方法、配置选项和开发指南,请查看测试目录下的 README.md 文档: + +- 文档位置:`user/apps/tests/syscall/gvisor/README.md` + diff --git a/docs/kernel/ktest/index.rst b/docs/kernel/ktest/index.rst index bd73448b9..034023bed 100644 --- a/docs/kernel/ktest/index.rst +++ b/docs/kernel/ktest/index.rst @@ -17,3 +17,5 @@ :maxdepth: 1 :caption: 目录 + gvisor_syscall_test.rst + diff --git a/user/Makefile b/user/Makefile index 1f720d748..10b1f9c9d 100644 --- a/user/Makefile +++ b/user/Makefile @@ -2,7 +2,7 @@ user_sub_dirs = apps DADK_VERSION=$(shell dadk -V | awk 'END {print $$2}') # 最小的DADK版本 -MIN_DADK_VERSION = 0.4.0 +MIN_DADK_VERSION = 0.5.0 DADK_CACHE_DIR = $(ROOT_PATH)/bin/dadk_cache ECHO: diff --git a/user/apps/tests/syscall/gvisor/.gitignore b/user/apps/tests/syscall/gvisor/.gitignore new file mode 100644 index 000000000..2d9d34973 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/.gitignore @@ -0,0 +1,2 @@ +/tests/ +/results/ diff --git a/user/apps/tests/syscall/gvisor/Makefile b/user/apps/tests/syscall/gvisor/Makefile new file mode 100644 index 000000000..0eb8afae0 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/Makefile @@ -0,0 +1,105 @@ +# gvisor系统调用测试 Makefile +# 用于DragonOS项目 + +# Rust工具链配置 +TOOLCHAIN="+nightly-2025-08-10-x86_64-unknown-linux-gnu" +RUSTFLAGS+="" + +ifdef DADK_CURRENT_BUILD_DIR +# 如果是在dadk中编译,那么安装到dadk的安装目录中 +INSTALL_DIR = $(DADK_CURRENT_BUILD_DIR) +else +# 如果是在本地编译,那么安装到当前目录下的install目录中 +INSTALL_DIR = ./install +endif + +ifeq ($(ARCH), x86_64) + export RUST_TARGET=x86_64-unknown-linux-musl +else ifeq ($(ARCH), riscv64) + export RUST_TARGET=riscv64gc-unknown-linux-gnu +else +# 默认为x86_64,用于本地编译 + export RUST_TARGET=x86_64-unknown-linux-musl +endif + +.PHONY: all build install download test list run clean help + +# 默认目标:构建并安装 +all: build install + +# 显示帮助信息 +help: + @echo "gvisor系统调用测试 Makefile" + @echo "" + @echo "可用目标:" + @echo " all - 构建并安装测试运行器(默认)" + @echo " build - 构建Rust测试运行器" + @echo " install - 安装测试运行器和必要文件" + @echo " download - 下载gvisor测试套件" + @echo " test - 运行白名单中的测试" + @echo " list - 列出所有可用测试" + @echo " run - 运行测试并传递参数(如:make run ARGS=\"-v epoll_test\")" + @echo " clean - 清理测试文件和结果" + @echo " help - 显示此帮助信息" + @echo "" + @echo "环境变量:" + @echo " SYSCALL_TEST_WORKDIR - 测试工作目录(默认: /tmp/gvisor_tests)" + @echo " TEST_TIMEOUT - 单个测试超时时间(默认: 300秒)" + +# 构建Rust测试运行器 +build: + @echo "构建gvisor测试运行器..." + @cd runner && RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET) --release + +# 下载测试套件 +download: + @echo "下载gvisor测试套件..." + @./download_tests.sh + +# 安装到目标目录 +install: build + @echo "安装gvisor测试套件到 $(INSTALL_DIR)" + @mkdir -p $(INSTALL_DIR) + # 安装Rust测试运行器二进制文件 + @cp -f runner/target/$(RUST_TARGET)/release/runner $(INSTALL_DIR)/gvisor-test-runner + # 安装测试配置文件 + @cp -f whitelist.txt $(INSTALL_DIR)/ + @cp -rf blocklists $(INSTALL_DIR)/ + # 安装下载脚本(用于目标系统上下载测试) + @cp -f download_tests.sh $(INSTALL_DIR)/ + @chmod +x $(INSTALL_DIR)/download_tests.sh + @chmod +x $(INSTALL_DIR)/gvisor-test-runner + @echo "安装完成" + +# 运行测试 +test: build + @echo "运行gvisor系统调用测试..." + @if [ ! -d tests ]; then \ + echo "测试套件不存在,正在下载..."; \ + ./download_tests.sh; \ + fi + @./runner/target/$(RUST_TARGET)/release/runner + +# 列出所有测试 +list: build + @if [ ! -d tests ]; then \ + echo "测试套件不存在,正在下载..."; \ + ./download_tests.sh; \ + fi + @./runner/target/$(RUST_TARGET)/release/runner --list + +# 运行测试并传递参数 +run: build + @if [ ! -d tests ]; then \ + echo "测试套件不存在,正在下载..."; \ + ./download_tests.sh; \ + fi + @./runner/target/$(RUST_TARGET)/release/runner $(ARGS) + +# 清理 +clean: + @echo "清理测试文件和结果..." + @rm -rf results/ + @rm -f gvisor-syscalls-tests.tar.xz + @cd runner && cargo clean + @echo "清理完成" diff --git a/user/apps/tests/syscall/gvisor/README.md b/user/apps/tests/syscall/gvisor/README.md new file mode 100644 index 000000000..e157365f7 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/README.md @@ -0,0 +1,306 @@ +# gvisor系统调用测试套件 + +这是DragonOS项目中用于运行gvisor系统调用测试的自动化工具。 + +测试用例仓库:https://cnb.cool/DragonOS-Community/test-suites + +## 概述 + +gvisor是Google开发的容器运行时沙箱,它包含了大量的系统调用兼容性测试。这些测试可以用来验证操作系统的系统调用实现是否符合Linux标准。 + +本测试框架默认启用**白名单模式**,只运行`whitelist.txt`文件中指定的测试程序,这样可以逐步验证DragonOS的系统调用实现。同时,每个测试程序内部的测试用例可以通过`blocklists/`目录中的黑名单文件进行过滤。 + +## 快速开始 + +### 1. 下载并运行白名单测试 + +```bash +# 进入gvisor测试目录 +cd user/apps/tests/syscall/gvisor + +# 运行白名单中的测试(默认行为,会自动下载测试套件) +make test + +# 运行所有测试(包括非白名单) +make run ARGS="--no-whitelist" +``` + +### 2. 仅下载测试套件 + +```bash +make download +# 或者直接运行脚本 +./download_tests.sh +``` + +### 3. 查看可用测试 + +```bash +make list +``` + +## 详细使用方法 + +### 基本使用 + +通过Makefile可以方便地运行测试: + +```bash +# 显示所有可用命令 +make help + +# 构建并安装测试运行器 +make + +# 下载测试套件 +make download + +# 运行白名单中的测试(自动下载测试套件) +make test + +# 列出所有可用测试 +make list + +# 运行测试并传递参数 +make run ARGS="-v" +make run ARGS="epoll_test" +make run ARGS="-j 4 --no-whitelist" + +# 清理测试结果 +make clean +``` + +### 使用示例 + +```bash +# 运行特定测试 +make run ARGS="epoll_test" + +# 使用模式匹配运行多个测试 +make run ARGS="socket*" + +# 详细输出模式 +make run ARGS="-v" + +# 并行运行4个测试 +make run ARGS="-j 4" + +# 禁用白名单,运行所有测试程序 +make run ARGS="--no-whitelist" + +# 忽略所有blocklist文件 +make run ARGS="--no-blocklist" + +# 设置超时时间为60秒 +make run ARGS="-t 60" + +# 组合多个参数 +make run ARGS="-v -j 2 epoll_test" +``` + +## 添加新的测试 + +### 添加测试程序到白名单 + +1. 编辑 `whitelist.txt` 文件 +2. 每行添加一个测试程序名称(不带路径) +3. 支持注释(以 `#` 开头) + +示例: +```text +# 核心系统调用测试 +socket_test +epoll_test +fcntl_test +ioctl_test + +# 文件系统测试 +open_test +stat_test +mmap_test +``` + +### 创建blocklist过滤测试用例 + +对于每个测试程序,可以创建blocklist文件来屏蔽特定的测试用例: + +1. 在 `blocklists/` 目录下创建与测试程序同名的文件 +2. 每行添加要屏蔽的测试用例名称 +3. 支持注释和空行 + +示例blocklist文件(`blocklists/socket_test`): +```text +# 屏蔽IPv6相关测试(DragonOS暂不支持IPv6) +SocketTest.IPv6* +SocketTest.IPv6Socket* + +# 屏蔽需要特殊权限的测试 +SocketTest.PrivilegedSocket + +# 屏蔽已知不稳定的测试 +SocketTest.UnimplementedFeature +``` + +### Blocklist文件格式 + +- 每行一个测试用例名称或模式 +- 支持通配符(`*` 匹配任意字符) +- 注释以 `#` 开头 +- 空行会被忽略 +- 测试用例名称通常格式为 `TestSuite.TestName` + +## Blocklist机制详解 + +### 什么是Blocklist + +Blocklist用于屏蔽某些在当前环境下不适用或不稳定的测试子用例。这对于逐步完善系统调用支持非常有用。 + +### Blocklist的工作原理 + +1. 当运行测试时,测试运行器会自动查找与测试程序同名的blocklist文件 +2. 文件位于 `blocklists/` 目录下 +3. 支持多个额外的blocklist目录(通过 `--extra-blocklist` 参数) +4. 匹配的测试用例会被跳过,不计入统计结果 + +### 示例:完整的测试配置 + +假设我们要添加对 `epoll_test` 的支持: + +1. **添加到白名单** (`whitelist.txt`): + ```text + epoll_test + ``` + +2. **创建blocklist** (`blocklists/epoll_test`): + ```text + # 屏蔽超时测试(需要更精确的时间控制) + EpollTest.Timeout* + + # 屏蔽循环测试(可能导致死锁) + EpollTest.CycleOfOneDisallowed + EpollTest.CycleOfThreeDisallowed + + # 屏蔽信号竞争测试 + # UnblockWithSignal contains races. Better not to enable it. + EpollTest.UnblockWithSignal + ``` + +3. **运行测试**: + ```bash + # 使用Makefile运行所有白名单测试(会自动应用blocklist) + make test + + # 只运行特定测试 + make run ARGS="epoll_test" + + # 查看详细输出 + make run ARGS="-v epoll_test" + ``` + +## 文件结构 + +``` +user/apps/tests/syscall/gvisor/ +├── download_tests.sh # 下载脚本 +├── Makefile # Make构建文件 +├── README.md # 说明文档 +├── whitelist.txt # 测试程序白名单 +├── runner/ # Rust测试运行器 +│ ├── Cargo.toml +│ ├── Makefile +│ └── src/ +│ ├── main.rs +│ └── lib_sync.rs +├── blocklists/ # Blocklist目录 +│ ├── README.md # Blocklist说明 +│ └── epoll_test # 示例blocklist +├── tests/ # 测试可执行文件(下载后生成) +└── results/ # 测试结果(运行后生成) + ├── failed_cases.txt # 失败用例列表 + ├── test_report.txt # 测试报告 + └── *.output # 各测试的详细输出 +``` + +## 环境变量 + +- `SYSCALL_TEST_WORKDIR`: 测试工作目录,默认为`/tmp/gvisor_tests` +- `TEST_TIMEOUT`: 单个测试的超时时间,默认300秒 +- `RUSTFLAGS`: Rust编译器标志 + +## 测试报告 + +测试完成后会生成: + +1. **控制台输出**: 实时显示测试进度和结果 +2. **测试报告**: `results/test_report.txt` - 包含统计信息和失败用例 +3. **失败用例列表**: `results/failed_cases.txt` - 仅包含失败的测试名称 +4. **详细输出**: `results/*.output` - 每个测试的详细输出 + +## 开发者指南 + +### 编译和开发 + +```bash +# 构建Rust测试运行器 +make build + +# 安装到指定目录 +make install +``` + +### 性能提示 + +- 默认串行运行测试,确保稳定性 +- 如需并行测试,使用 `-j` 参数(谨慎使用) +- 合理设置超时时间,避免长时间等待 +- 使用blocklist跳过已知问题的测试 + +## 注意事项 + +1. **网络依赖**: 首次运行 `make test` 时会自动下载测试套件 +2. **存储空间**: 测试套件解压后约占用1.1GB空间 +3. **运行时间**: 完整测试可能需要较长时间,建议先运行部分测试 +4. **权限要求**: 某些测试可能需要特定的系统权限 +5. **自动下载**: 运行 `make test` 或 `make list` 时会自动下载所需的测试套件 + +## 故障排除 + +### 下载失败 +```bash +# 检查网络连接 +wget --spider https://cnb.cool/DragonOS-Community/test-suites/-/releases/download/release_20250626/gvisor-syscalls-tests.tar.xz + +# 手动下载并放置 +wget -O gvisor-syscalls-tests.tar.xz https://cnb.cool/DragonOS-Community/test-suites/-/releases/download/release_20250626/gvisor-syscalls-tests.tar.xz +``` + +### MD5校验失败 +```bash +# 重新下载文件 +rm -f gvisor-syscalls-tests.tar.xz +./download_tests.sh +``` + +### 测试运行失败 +1. 检查测试二进制文件是否存在 +2. 确认可执行权限 +3. 查看详细输出了解失败原因 + +### 测试失败过多 +1. 检查系统调用实现是否完整 +2. 调整blocklist屏蔽已知问题 +3. 检查测试环境配置 +4. 考虑增加超时时间 + +## 贡献 + +如果发现测试相关的问题或有改进建议,请: + +1. 提交issue描述问题 +2. 更新相应的blocklist文件 +3. 提交patch修复脚本问题 +4. 帮助完善测试覆盖 + +## 许可证 + +本测试框架代码遵循GPLv2许可证。gvisor测试套件本身遵循其原始许可证。 diff --git a/user/apps/tests/syscall/gvisor/blocklists/README.md b/user/apps/tests/syscall/gvisor/blocklists/README.md new file mode 100644 index 000000000..9abc6a517 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/blocklists/README.md @@ -0,0 +1,43 @@ +# Blocklist 目录 + +这个目录包含用于屏蔽特定gvisor测试子用例的blocklist文件。 + +## 文件格式 + +每个blocklist文件对应一个测试可执行文件,文件名应与测试可执行文件名相同。 + +例如: +- `socket_test` - 对应测试可执行文件 `socket_test` +- `pipe_test` - 对应测试可执行文件 `pipe_test` + +## 内容格式 + +blocklist文件中每一行包含一个要屏蔽的测试用例名称: + +``` +# 这是注释行,会被忽略 +# 屏蔽某个特定的测试用例 +TestCase.SpecificTest +# 屏蔽某个测试套件下的所有测试 +TestSuite.* +# 屏蔽包含特定模式的测试 +*PatternName* +``` + +## 注意事项 + +- 以 `#` 开头的行会被视为注释并忽略 +- 空行会被忽略 +- 支持通配符模式匹配 +- 测试用例名称格式通常为 `TestSuite.TestCase` + +## 示例 + +如果要屏蔽socket_test中的某些测试,创建文件`socket_test`: + +``` +# 屏蔽IPv6相关的测试(暂不支持) +SocketTest.IPv6* +# 屏蔽特定的不稳定测试 +SocketTest.UnstableTest +``` \ No newline at end of file diff --git a/user/apps/tests/syscall/gvisor/blocklists/epoll_test b/user/apps/tests/syscall/gvisor/blocklists/epoll_test new file mode 100644 index 000000000..0c2a7b992 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/blocklists/epoll_test @@ -0,0 +1,7 @@ +# EpollTest.Timeout_NoRandomSave +# EpollTest.CycleOfOneDisallowed +# EpollTest.CycleOfThreeDisallowed +# `UnblockWithSignal` contains races. Better not to enable it. +# See https://github.com/asterinas/asterinas/pull/1035 for details. +# EpollTest.UnblockWithSignal + diff --git a/user/apps/tests/syscall/gvisor/download_tests.sh b/user/apps/tests/syscall/gvisor/download_tests.sh new file mode 100755 index 000000000..3fbfd80db --- /dev/null +++ b/user/apps/tests/syscall/gvisor/download_tests.sh @@ -0,0 +1,233 @@ +#!/bin/bash + +# gvisor系统调用测试套件下载和校验脚本 +# 用于DragonOS项目 + +set -e + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +TESTS_DIR="$SCRIPT_DIR/tests" +TEST_ARCHIVE="gvisor-syscalls-tests.tar.xz" +TEST_URL="https://cnb.cool/DragonOS-Community/test-suites/-/releases/download/release_20250626/$TEST_ARCHIVE" +MD5SUM_URL="https://cnb.cool/DragonOS-Community/test-suites/-/releases/download/release_20250626/$TEST_ARCHIVE.md5sum" + +# 颜色定义 +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +print_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查命令是否存在 +check_command() { + if ! command -v "$1" &> /dev/null; then + print_error "命令 '$1' 未找到,请安装相应软件包" + exit 1 + fi +} + +# 检查必要的命令 +check_dependencies() { + print_info "检查依赖..." + check_command wget + check_command md5sum + check_command tar +} + +# 检查测试套件是否已存在且完整 +check_existing_tests() { + if [ -d "$TESTS_DIR" ] && [ "$(find "$TESTS_DIR" -name "*_test" | wc -l)" -gt 0 ]; then + print_info "发现已存在的测试套件" + return 0 + else + return 1 + fi +} + +# 下载文件 +download_file() { + local url="$1" + local output="$2" + local quiet_mode="${3:-false}" + + if [ "$quiet_mode" = "true" ]; then + # 静默下载(用于小文件如MD5) + if wget -q -O "$output" "$url" 2>/dev/null; then + return 0 + else + return 1 + fi + else + # 显示进度的下载(用于大文件) + print_info "从 $url 下载文件..." + if wget -q --show-progress -O "$output" "$url"; then + print_info "下载完成: $output" + return 0 + else + print_error "下载失败: $url" + return 1 + fi + fi +} + +# 获取期望的MD5值 +get_expected_md5() { + # 将日志输出重定向到stderr,避免混入返回值 + print_info "获取期望的MD5校验和..." >&2 + local temp_md5_file=$(mktemp) + + # 确保在函数退出时清理临时文件 + trap "rm -f '$temp_md5_file'" RETURN + + if download_file "$MD5SUM_URL" "$temp_md5_file" "true"; then + # 解析MD5文件,格式通常是: "md5hash filename" + local expected_md5=$(head -1 "$temp_md5_file" | cut -d' ' -f1) + + if [ -n "$expected_md5" ] && [ ${#expected_md5} -eq 32 ]; then + echo "$expected_md5" + return 0 + else + print_error "MD5文件格式无效或为空" >&2 + return 1 + fi + else + print_error "无法下载MD5校验和文件" >&2 + return 1 + fi +} + +# 验证MD5校验和 +verify_md5() { + local file="$1" + local expected="$2" + + print_info "验证MD5校验和..." + local actual_md5=$(md5sum "$file" | cut -d' ' -f1) + + if [ "$actual_md5" = "$expected" ]; then + print_info "MD5校验和验证成功" + return 0 + else + print_error "MD5校验和验证失败" + print_error "期望: $expected" + print_error "实际: $actual_md5" + return 1 + fi +} + +# 解压测试套件 +extract_tests() { + local archive="$1" + + print_info "解压测试套件..." + mkdir -p "$TESTS_DIR" + + if tar -xf "$archive" -C "$TESTS_DIR" --strip-components=1; then + print_info "解压完成" + return 0 + else + print_error "解压失败" + return 1 + fi +} + +# 清理临时文件 +cleanup_temp_files() { + print_info "清理临时文件..." + rm -f "$SCRIPT_DIR/$TEST_ARCHIVE" +} + +# 主函数 +main() { + local skip_if_exists=false + local force_download=false + + # 检查参数 + for arg in "$@"; do + case "$arg" in + --skip-if-exists) + skip_if_exists=true + ;; + --force-download) + force_download=true + ;; + esac + done + + print_info "开始检查和下载gvisor系统调用测试套件" + + # 检查依赖 + check_dependencies + + # 检查是否已存在测试套件 + if check_existing_tests; then + if [ "$skip_if_exists" = true ]; then + print_info "测试套件已存在,跳过下载" + exit 0 + elif [ "$force_download" = true ]; then + print_warn "强制重新下载,删除现有测试套件..." + rm -rf "$TESTS_DIR" + else + read -p "测试套件已存在,是否重新下载?(y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_info "使用现有测试套件" + exit 0 + fi + print_warn "删除现有测试套件..." + rm -rf "$TESTS_DIR" + fi + fi + + # 下载测试套件 + if ! download_file "$TEST_URL" "$SCRIPT_DIR/$TEST_ARCHIVE"; then + exit 1 + fi + + # 获取期望的MD5值 + local expected_md5 + if ! expected_md5=$(get_expected_md5); then + cleanup_temp_files + exit 1 + fi + + print_info "期望的MD5校验和: $expected_md5" + + # 验证MD5 + if ! verify_md5 "$SCRIPT_DIR/$TEST_ARCHIVE" "$expected_md5"; then + cleanup_temp_files + exit 1 + fi + + # 解压测试套件 + if ! extract_tests "$SCRIPT_DIR/$TEST_ARCHIVE"; then + cleanup_temp_files + exit 1 + fi + + # 清理临时文件 + cleanup_temp_files + + # 统计测试数量 + local test_count=$(find "$TESTS_DIR" -name "*_test" | wc -l) + print_info "测试套件安装完成,共包含 $test_count 个测试用例" + + # 设置执行权限 + find "$TESTS_DIR" -name "*_test" -exec chmod +x {} \; + + print_info "gvisor测试套件准备就绪" +} + +# 运行主函数 +main "$@" \ No newline at end of file diff --git a/user/apps/tests/syscall/gvisor/runner/.gitignore b/user/apps/tests/syscall/gvisor/runner/.gitignore new file mode 100644 index 000000000..786a31746 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/runner/.gitignore @@ -0,0 +1,21 @@ +# Rust构建产物 +/target/ +Cargo.lock + +# IDE文件 +.vscode/ +.idea/ +*.swp +*.swo + +# 操作系统文件 +.DS_Store +Thumbs.db + +# 临时文件 +*~ +*.tmp +*.bak + +# 测试输出文件(如果在此目录运行) +/results/ \ No newline at end of file diff --git a/user/apps/tests/syscall/gvisor/runner/Cargo.toml b/user/apps/tests/syscall/gvisor/runner/Cargo.toml new file mode 100644 index 000000000..a762c06b8 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/runner/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "gvisor-test-runner" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "runner" +path = "src/main.rs" + +[dependencies] +clap = { version = "4.0", features = ["derive"] } +anyhow = "1.0" +regex = "1.0" +chrono = { version = "0.4", features = ["serde"] } +log = "0.4" +env_logger = "0.10" + +[profile.release] +lto = true diff --git a/user/apps/tests/syscall/gvisor/runner/Makefile b/user/apps/tests/syscall/gvisor/runner/Makefile new file mode 100644 index 000000000..bdaa42c16 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/runner/Makefile @@ -0,0 +1,69 @@ +# Makefile for gvisor-test-runner + +# 默认目标 +.PHONY: all build release clean install test help + +# 默认构建 +all: build + +# 开发构建 +build: + cargo build + +# Release构建(推荐) +release: + cargo build --release + +# 清理构建文件 +clean: + cargo clean + +# 运行测试(如果有单元测试) +test: + cargo test + +# 检查代码 +check: + cargo check + +# 格式化代码 +fmt: + cargo fmt + +# 代码检查 +clippy: + cargo clippy + +# 列出测试用例 +list: release + ./target/release/runner --list + +# 运行测试(使用默认配置) +run: release + ./target/release/runner + +# 显示帮助 +help: release + ./target/release/runner --help + +# 显示此Makefile的帮助 +show-help: + @echo "可用的make目标:" + @echo " all - 构建开发版本(默认)" + @echo " build - 构建开发版本" + @echo " release - 构建release版本(推荐)" + @echo " clean - 清理构建文件" + @echo " install - 安装到系统(需要sudo)" + @echo " test - 运行单元测试" + @echo " check - 检查代码编译" + @echo " fmt - 格式化代码" + @echo " clippy - 运行代码检查" + @echo " list - 列出所有测试用例" + @echo " run - 运行测试(默认配置)" + @echo " help - 显示程序帮助" + @echo "" + @echo "使用示例:" + @echo " make release # 构建release版本" + @echo " make list # 列出测试用例" + @echo " make run # 运行白名单中的测试" + @echo " ./target/release/runner --help # 查看详细帮助" diff --git a/user/apps/tests/syscall/gvisor/runner/src/lib_sync.rs b/user/apps/tests/syscall/gvisor/runner/src/lib_sync.rs new file mode 100644 index 000000000..230b85141 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/runner/src/lib_sync.rs @@ -0,0 +1,594 @@ +use anyhow::Result; +use chrono::{DateTime, Local}; +use std::{ + collections::HashSet, + fs::{self, File}, + io::{BufRead, BufReader, Write}, + path::{Path, PathBuf}, + process::Command, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + time::Instant, +}; + +/// 测试统计信息 +#[derive(Debug, Default)] +pub struct TestStats { + total: AtomicUsize, + passed: AtomicUsize, + failed: AtomicUsize, + skipped: AtomicUsize, +} + +impl TestStats { + pub fn increment_total(&self) { + self.total.fetch_add(1, Ordering::Relaxed); + } + + pub fn increment_passed(&self) { + self.passed.fetch_add(1, Ordering::Relaxed); + } + + pub fn increment_failed(&self) { + self.failed.fetch_add(1, Ordering::Relaxed); + } + + #[allow(dead_code)] + pub fn increment_skipped(&self) { + self.skipped.fetch_add(1, Ordering::Relaxed); + } + + pub fn get_totals(&self) -> (usize, usize, usize, usize) { + ( + self.total.load(Ordering::Relaxed), + self.passed.load(Ordering::Relaxed), + self.failed.load(Ordering::Relaxed), + self.skipped.load(Ordering::Relaxed), + ) + } +} + +/// 测试运行器配置 +#[derive(Debug, Clone)] +pub struct Config { + pub verbose: bool, + pub timeout: u64, + pub parallel: usize, + pub use_blocklist: bool, + pub use_whitelist: bool, + pub whitelist_file: PathBuf, + pub tests_dir: PathBuf, + pub blocklists_dir: PathBuf, + pub results_dir: PathBuf, + pub temp_dir: PathBuf, + pub extra_blocklist_dirs: Vec, + pub test_patterns: Vec, +} + +impl Default for Config { + fn default() -> Self { + let script_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + Self { + verbose: false, + timeout: 300, + parallel: 1, + use_blocklist: true, + use_whitelist: true, + whitelist_file: script_dir.join("whitelist.txt"), + tests_dir: script_dir.join("tests"), + blocklists_dir: script_dir.join("blocklists"), + results_dir: script_dir.join("results"), + temp_dir: PathBuf::from( + std::env::var("SYSCALL_TEST_WORKDIR") + .unwrap_or_else(|_| "/tmp/gvisor_tests".to_string()), + ), + extra_blocklist_dirs: Vec::new(), + test_patterns: Vec::new(), + } + } +} + +/// 颜色输出辅助函数(简化版) +pub fn print_colored(color: &str, prefix: &str, msg: &str) { + match color { + "green" => println!("\x1b[32m[{}]\x1b[0m {}", prefix, msg), + "yellow" => println!("\x1b[33m[{}]\x1b[0m {}", prefix, msg), + "red" => eprintln!("\x1b[31m[{}]\x1b[0m {}", prefix, msg), + "blue" => println!("\x1b[34m[{}]\x1b[0m {}", prefix, msg), + _ => println!("[{}] {}", prefix, msg), + } +} + +/// gvisor系统调用测试运行器 +pub struct TestRunner { + pub config: Config, + pub stats: Arc, +} + +impl TestRunner { + pub fn new(config: Config) -> Self { + Self { + config, + stats: Arc::new(TestStats::default()), + } + } + + /// 打印信息日志 + fn print_info(&self, msg: &str) { + print_colored("green", "INFO", msg); + } + + /// 打印警告日志 + fn print_warn(&self, msg: &str) { + print_colored("yellow", "WARN", msg); + } + + /// 打印错误日志 + fn print_error(&self, msg: &str) { + print_colored("red", "ERROR", msg); + } + + /// 打印测试日志 + fn print_test(&self, msg: &str) { + print_colored("blue", "TEST", msg); + } + + /// 检查测试套件是否存在 + pub fn check_test_suite(&self) -> Result<()> { + if !self.config.tests_dir.exists() { + anyhow::bail!( + "测试目录不存在: {:?}\n请先运行 ./download_tests.sh 下载测试套件", + self.config.tests_dir + ); + } + + let test_files: Vec<_> = fs::read_dir(&self.config.tests_dir)? + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry.path().is_file() && entry.file_name().to_string_lossy().ends_with("_test") + }) + .collect(); + + if test_files.is_empty() { + anyhow::bail!("测试套件未找到\n请先运行 ./download_tests.sh 下载测试套件"); + } + + Ok(()) + } + + /// 创建必要的目录 + pub fn setup_directories(&self) -> Result<()> { + fs::create_dir_all(&self.config.results_dir)?; + fs::create_dir_all(&self.config.temp_dir)?; + fs::create_dir_all(&self.config.blocklists_dir)?; + Ok(()) + } + + /// 读取白名单中的测试程序 + fn get_whitelist_tests(&self) -> Result> { + if !self.config.whitelist_file.exists() { + anyhow::bail!("白名单文件不存在: {:?}", self.config.whitelist_file); + } + + let file = File::open(&self.config.whitelist_file)?; + let reader = BufReader::new(file); + let mut tests = HashSet::new(); + + for line in reader.lines() { + let line = line?; + let line = line.trim(); + if !line.is_empty() && !line.starts_with('#') { + tests.insert(line.to_string()); + } + } + + Ok(tests) + } + + /// 检查测试是否在白名单中 + fn is_test_whitelisted(&self, test_name: &str) -> bool { + if !self.config.use_whitelist { + return true; + } + + match self.get_whitelist_tests() { + Ok(whitelist) => whitelist.contains(test_name), + Err(_) => false, + } + } + + /// 获取测试的blocklist + fn get_test_blocklist(&self, test_name: &str) -> Vec { + if !self.config.use_blocklist { + return Vec::new(); + } + + let mut blocked_subtests = Vec::new(); + + // 检查主blocklist目录 + let blocklist_file = self.config.blocklists_dir.join(test_name); + if let Ok(content) = self.read_blocklist_file(&blocklist_file) { + blocked_subtests.extend(content); + } + + // 检查额外的blocklist目录 + for extra_dir in &self.config.extra_blocklist_dirs { + let extra_blocklist = extra_dir.join(test_name); + if let Ok(content) = self.read_blocklist_file(&extra_blocklist) { + blocked_subtests.extend(content); + } + } + + blocked_subtests + } + + /// 读取blocklist文件 + fn read_blocklist_file(&self, path: &Path) -> Result> { + if !path.exists() { + return Ok(Vec::new()); + } + + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut blocked = Vec::new(); + + for line in reader.lines() { + let line = line?; + let line = line.trim(); + if !line.is_empty() && !line.starts_with('#') { + blocked.push(line.to_string()); + } + } + + Ok(blocked) + } + + /// 获取要运行的测试列表 + pub fn get_test_list(&self) -> Result> { + // 获取所有测试文件 + let mut all_tests = Vec::new(); + for entry in fs::read_dir(&self.config.tests_dir)? { + let entry = entry?; + if entry.path().is_file() { + let file_name = entry.file_name(); + let file_name_str = file_name.to_string_lossy(); + if file_name_str.ends_with("_test") { + all_tests.push(file_name_str.to_string()); + } + } + } + + // 应用白名单过滤 + let mut candidate_tests = Vec::new(); + if self.config.use_whitelist { + for test in &all_tests { + if self.is_test_whitelisted(test) { + candidate_tests.push(test.clone()); + } + } + + if candidate_tests.is_empty() { + self.print_warn("没有测试通过白名单过滤"); + return Ok(Vec::new()); + } + + if self.config.verbose { + self.print_info(&format!( + "白名单过滤后有 {} 个测试可用", + candidate_tests.len() + )); + } + } else { + candidate_tests = all_tests; + } + + // 如果没有指定模式,返回候选测试 + if self.config.test_patterns.is_empty() { + candidate_tests.sort(); + return Ok(candidate_tests); + } + + // 根据模式过滤测试 + let mut filtered_tests = HashSet::new(); + for pattern in &self.config.test_patterns { + for test in &candidate_tests { + if test == pattern { + filtered_tests.insert(test.clone()); + } + } + } + + let mut result: Vec<_> = filtered_tests.into_iter().collect(); + result.sort(); + Ok(result) + } + + /// 运行单个测试 + pub fn run_single_test(&self, test_name: &str) -> Result { + println!("[DEBUG] 开始运行测试: {}", test_name); + let test_path = self.config.tests_dir.join(test_name); + println!("[DEBUG] 测试路径: {:?}", test_path); + + if !test_path.exists() || !test_path.is_file() { + self.print_warn(&format!("测试不存在或不可执行: {}", test_name)); + return Ok(false); + } + + self.print_test(&format!("运行测试用例: {}", test_name)); + + // 获取blocklist + let blocked_subtests = self.get_test_blocklist(test_name); + + // 结果输出文件(使用绝对路径,避免工作目录切换影响) + let output_file = self + .config + .results_dir + .join(format!("{}.output", test_name)); + + println!("[DEBUG] 工作目录: {:?}", self.config.tests_dir); + println!("[DEBUG] TEST_TMPDIR: {:?}", self.config.temp_dir); + println!("[DEBUG] 直接执行: {:?}", test_path); + if !blocked_subtests.is_empty() { + println!("[DEBUG] gtest_filter: -{}", blocked_subtests.join(":")); + } + + // 确保结果目录存在 + if let Err(e) = fs::create_dir_all(&self.config.results_dir) { + self.print_error(&format!("创建结果目录失败: {}", e)); + } + + // 打开输出文件,并将 stdout/stderr 重定向到该文件 + let out = File::create(&output_file); + if let Err(e) = out.as_ref() { + self.print_error(&format!("创建输出文件失败: {:?}, 错误: {}", output_file, e)); + } + let out = out?; + let err = out.try_clone()?; + + // 构造并执行命令(不使用 shell,不捕获输出,不创建管道) + let start_time = Instant::now(); + let mut cmd = Command::new(&test_path); + if !blocked_subtests.is_empty() { + cmd.arg("--gtest_filter") + .arg(format!("-{}", blocked_subtests.join(":"))); + } + let status = cmd + .current_dir(&self.config.tests_dir) + .env("TEST_TMPDIR", &self.config.temp_dir) + .stdout(std::process::Stdio::from(out)) + .stderr(std::process::Stdio::from(err)) + .status(); + + // 清理临时目录 + let _ = fs::remove_dir_all(&self.config.temp_dir); + let _ = fs::create_dir_all(&self.config.temp_dir); + + let duration = start_time.elapsed(); + match status { + Ok(s) if s.success() => { + self.print_info(&format!( + "✓ {} 通过 ({:.2}s)", + test_name, + duration.as_secs_f64() + )); + // 将输出文件尾部打印一点,便于快速查看 + if let Ok(content) = fs::read_to_string(&output_file) { + let tail: String = content + .lines() + .rev() + .take(10) + .collect::>() + .into_iter() + .rev() + .map(|s| format!("{}\n", s)) + .collect(); + if !tail.is_empty() { + println!("[DEBUG] 输出尾部: \n{}", tail); + } + } + Ok(true) + } + Ok(s) => { + self.print_error(&format!( + "✗ {} 失败 ({:.2}s), 退出码: {:?}", + test_name, + duration.as_secs_f64(), + s.code() + )); + // 打印错误输出尾部 + if let Ok(content) = fs::read_to_string(&output_file) { + let tail: String = content + .lines() + .rev() + .take(20) + .collect::>() + .into_iter() + .rev() + .map(|s| format!("{}\n", s)) + .collect(); + if !tail.is_empty() { + println!("[DEBUG] 错误输出尾部: \n{}", tail); + } + } + Ok(false) + } + Err(e) => { + self.print_error(&format!("✗ {} 执行错误: {}", test_name, e)); + Ok(false) + } + } + } + + /// 运行所有测试 + pub fn run_all_tests(&self) -> Result<()> { + let test_list = self.get_test_list()?; + + if test_list.is_empty() { + self.print_warn("没有找到匹配的测试用例"); + return Ok(()); + } + + self.print_info(&format!("准备运行 {} 个测试用例", test_list.len())); + + // 初始化结果文件 + let failed_cases_file = self.config.results_dir.join("failed_cases.txt"); + let mut failed_cases = File::create(&failed_cases_file)?; + + // 运行测试 + for test_name in test_list { + self.stats.increment_total(); + + match self.run_single_test(&test_name) { + Ok(true) => { + self.stats.increment_passed(); + } + Ok(false) => { + self.stats.increment_failed(); + writeln!(failed_cases, "{}", test_name)?; + } + Err(e) => { + self.stats.increment_failed(); + writeln!(failed_cases, "{}", test_name)?; + self.print_error(&format!("测试 {} 出错: {}", test_name, e)); + } + } + + println!("---"); + } + + Ok(()) + } + + /// 生成测试报告 + pub fn generate_report(&self) -> Result<()> { + let report_file = self.config.results_dir.join("test_report.txt"); + let mut file = File::create(&report_file)?; + let (total, passed, failed, _skipped) = self.stats.get_totals(); + + let now: DateTime = Local::now(); + let success_rate = if total > 0 { + passed as f64 * 100.0 / total as f64 + } else { + 0.0 + }; + + let report = format!( + "gvisor系统调用测试报告\n\ + ==========================\n\ + 测试时间: {}\n\ + 测试目录: {:?}\n\ + \n\ + 测试统计:\n\ + 总测试数: {}\n\ + 通过: {}\n\ + 失败: {}\n\ + 成功率: {:.2}%\n\ + \n", + now.format("%Y-%m-%d %H:%M:%S"), + self.config.tests_dir, + total, + passed, + failed, + success_rate + ); + + file.write_all(report.as_bytes())?; + println!("{}", report); + + if failed > 0 { + let failed_cases_file = self.config.results_dir.join("failed_cases.txt"); + if failed_cases_file.exists() { + let failed_content = fs::read_to_string(&failed_cases_file)?; + let failed_section = format!("失败的测试用例:\n{}", failed_content); + file.write_all(failed_section.as_bytes())?; + println!("{}", failed_section); + } + } + + Ok(()) + } + + /// 显示测试结果 + pub fn show_results(&self) { + let (total, passed, failed, _skipped) = self.stats.get_totals(); + + log::info!(""); + log::info!("==============================================="); + self.print_info("测试完成"); + log::info!( + "\x1b[32m{}\x1b[0m / \x1b[32m{}\x1b[0m 测试用例通过", + passed, + total + ); + + if failed > 0 { + log::info!("\x1b[31m{}\x1b[0m 个测试用例失败:", failed); + let failed_cases_file = self.config.results_dir.join("failed_cases.txt"); + if let Ok(content) = fs::read_to_string(&failed_cases_file) { + for line in content.lines() { + if !line.trim().is_empty() { + log::info!(" \x1b[31m[X]\x1b[0m {}", line); + } + } + } + } + + log::info!(""); + log::info!( + "详细报告保存在: {:?}", + self.config.results_dir.join("test_report.txt") + ); + } + + /// 列出所有测试用例 + pub fn list_tests(&self) -> Result<()> { + if !self.config.tests_dir.exists() { + self.print_error(&format!("测试目录不存在: {:?}", self.config.tests_dir)); + self.print_info("请先运行 ./download_tests.sh 下载测试套件"); + return Ok(()); + } + + if self.config.use_whitelist { + self.print_info(&format!( + "白名单模式 - 可运行的测试用例 (来自: {:?}):", + self.config.whitelist_file + )); + let test_list = self.get_test_list()?; + for test_name in &test_list { + log::info!(" \x1b[32m✓\x1b[0m {}", test_name); + } + + self.print_info("所有可用测试用例 (包括未在白名单中的):"); + for entry in fs::read_dir(&self.config.tests_dir)? { + let entry = entry?; + if entry.path().is_file() { + let file_name = entry.file_name(); + let file_name_str = file_name.to_string_lossy(); + if file_name_str.ends_with("_test") { + if self.is_test_whitelisted(&file_name_str) { + log::info!(" \x1b[32m✓\x1b[0m {} (在白名单中)", file_name_str); + } else { + log::info!(" \x1b[33m○\x1b[0m {} (不在白名单中)", file_name_str); + } + } + } + } + } else { + self.print_info("所有可用的测试用例:"); + for entry in fs::read_dir(&self.config.tests_dir)? { + let entry = entry?; + if entry.path().is_file() { + let file_name = entry.file_name(); + let file_name_str = file_name.to_string_lossy(); + if file_name_str.ends_with("_test") { + log::info!(" {}", file_name_str); + } + } + } + } + + Ok(()) + } +} diff --git a/user/apps/tests/syscall/gvisor/runner/src/main.rs b/user/apps/tests/syscall/gvisor/runner/src/main.rs new file mode 100644 index 000000000..dff1ff4b5 --- /dev/null +++ b/user/apps/tests/syscall/gvisor/runner/src/main.rs @@ -0,0 +1,159 @@ +use anyhow::Result; +use clap::{Arg, Command}; +use std::path::PathBuf; + +mod lib_sync; +use lib_sync::{Config, TestRunner}; + +fn main() -> Result<()> { + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Info) + .init(); + let app = Command::new("gvisor-test-runner") + .version("0.1.0") + .about("gvisor系统调用测试运行脚本 - Rust版本") + .arg( + Arg::new("help") + .short('h') + .long("help") + .action(clap::ArgAction::Help) + .help("显示此帮助信息"), + ) + .arg( + Arg::new("list") + .short('l') + .long("list") + .action(clap::ArgAction::SetTrue) + .help("列出所有可用的测试用例"), + ) + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .action(clap::ArgAction::SetTrue) + .help("详细输出模式"), + ) + .arg( + Arg::new("timeout") + .short('t') + .long("timeout") + .value_name("SEC") + .help("设置单个测试的超时时间(默认:300秒)") + .value_parser(clap::value_parser!(u64)), + ) + .arg( + Arg::new("parallel") + .short('j') + .long("parallel") + .value_name("NUM") + .help("并行运行的测试数量(默认:1)") + .value_parser(clap::value_parser!(usize)), + ) + .arg( + Arg::new("no-blocklist") + .long("no-blocklist") + .action(clap::ArgAction::SetTrue) + .help("忽略所有blocklist文件"), + ) + .arg( + Arg::new("extra-blocklist") + .long("extra-blocklist") + .value_name("DIR") + .action(clap::ArgAction::Append) + .help("指定额外的blocklist目录"), + ) + .arg( + Arg::new("no-whitelist") + .long("no-whitelist") + .action(clap::ArgAction::SetTrue) + .help("禁用白名单模式,运行所有测试程序"), + ) + .arg( + Arg::new("whitelist") + .long("whitelist") + .value_name("FILE") + .help("指定白名单文件路径(默认:whitelist.txt)"), + ) + .arg( + Arg::new("test-patterns") + .value_name("PATTERN") + .action(clap::ArgAction::Append) + .help("测试名称模式"), + ); + + let matches = app.get_matches(); + + // 解析配置 + let mut config = Config::default(); + + config.verbose = matches.get_flag("verbose"); + + if let Some(timeout) = matches.get_one::("timeout") { + config.timeout = *timeout; + } + + if let Some(parallel) = matches.get_one::("parallel") { + config.parallel = *parallel; + } + + config.use_blocklist = !matches.get_flag("no-blocklist"); + config.use_whitelist = !matches.get_flag("no-whitelist"); + + if let Some(whitelist_file) = matches.get_one::("whitelist") { + config.whitelist_file = PathBuf::from(whitelist_file); + } + + if let Some(extra_dirs) = matches.get_many::("extra-blocklist") { + config.extra_blocklist_dirs = extra_dirs.map(|s| PathBuf::from(s)).collect(); + } + + if let Some(patterns) = matches.get_many::("test-patterns") { + config.test_patterns = patterns.cloned().collect(); + } + + // 创建测试运行器 + let runner = TestRunner::new(config); + + // 处理特殊命令 + if matches.get_flag("list") { + runner.list_tests()?; + return Ok(()); + } + + log::info!("=============================="); + log::info!(" gvisor系统调用测试运行器"); + log::info!("=============================="); + + // 检查测试套件 + runner.check_test_suite()?; + + // 设置目录 + runner.setup_directories()?; + + log::info!("开始运行gvisor系统调用测试"); + + // 显示运行配置 + if runner.config.use_whitelist { + log::info!("白名单模式已启用: {:?}", runner.config.whitelist_file); + } + if !runner.config.use_blocklist { + log::info!("黑名单已禁用"); + } + + // 运行测试 + runner.run_all_tests()?; + + // 生成报告 + runner.generate_report()?; + + // 显示结果 + runner.show_results(); + + // 返回适当的退出码 + let (_, _, failed, _) = runner.stats.get_totals(); + if failed > 0 { + std::process::exit(1); + } + + Ok(()) +} diff --git a/user/apps/tests/syscall/gvisor/whitelist.txt b/user/apps/tests/syscall/gvisor/whitelist.txt new file mode 100644 index 000000000..ede6fe27d --- /dev/null +++ b/user/apps/tests/syscall/gvisor/whitelist.txt @@ -0,0 +1,30 @@ +# gvisor测试程序白名单 +# 每行一个测试程序名称,只有在此列表中的测试程序才会被执行 +# 以#开头的行为注释,空行会被忽略 + +# 基础系统调用测试 +read_test +#chdir_test + +# 文件系统相关测试 +#stat_test +#chmod_test +#chown_test + +# 进程相关测试 +#fork_test +#exec_test +#wait_test + +# 内存管理测试 +#mmap_test +#brk_test + +# 网络相关测试 +#bind_test +#connect_test +#listen_test + +# 信号处理测试 +#signal_test +#sigaction_test diff --git a/user/dadk/config/gvisor_syscall_tests-1.0.0.toml b/user/dadk/config/gvisor_syscall_tests-1.0.0.toml new file mode 100644 index 000000000..bb62ea684 --- /dev/null +++ b/user/dadk/config/gvisor_syscall_tests-1.0.0.toml @@ -0,0 +1,39 @@ +# 用户程序名称 +name = "gvisor syscall tests" +# 版本号 +version = "1.0.0" +# 用户程序描述信息 +description = "gVisor系统调用兼容性测试套件" +# (可选)是否只构建一次,如果为true,DADK会在构建成功后,将构建结果缓存起来,下次构建时,直接使用缓存的构建结果 +build-once = false +# (可选) 是否只安装一次,如果为true,DADK会在安装成功后,不再重复安装 +install-once = false +# 目标架构 +# 可选值:"x86_64", "aarch64", "riscv64" +target-arch = ["x86_64"] +# 任务源 +[task-source] +# 构建类型 +# 可选值:"build-from-source", "install-from-prebuilt" +type = "build-from-source" +# 构建来源 +# "build_from_source" 可选值:"git", "local", "archive" +# "install_from_prebuilt" 可选值:"local", "archive" +source = "local" +# 路径或URL +source-path = "user/apps/tests/syscall/gvisor" + +# 构建相关信息 +[build] +# (可选)构建命令 +build-command = "make install" + +# 安装相关信息 +[install] +# (可选)安装到DragonOS的路径 +in-dragonos-path = "/opt/tests/gvisor" + +# 清除相关信息 +[clean] +# (可选)清除命令 +clean-command = "make clean" \ No newline at end of file