强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

POSIX 标准详解教程 / 第十四章:合规测试

第十四章:合规测试

了解 POSIX 合规测试体系:测试套件、兼容性检查、自定义测试框架。


14.1 POSIX 合规测试概述

14.1.1 为什么需要合规测试

目的说明
验证标准兼容性确保代码在不同 POSIX 系统上行为一致
发现实现差异不同 libc/内核可能有不同的边界行为
回归测试修改代码后确认没有破坏标准行为
文档化行为测试用例就是行为文档

14.1.2 主要合规测试套件

测试套件维护者覆盖范围网址
LTP (Linux Test Project)SUSE/Red Hat/IBMLinux 内核和系统调用https://github.com/linux-test-project/ltp
POSIX Test SuiteAustin GroupPOSIX.1 接口https://github.com/mariotoffia/PosixTestSuite
glibc testsuiteGNUglibc 函数实现glibc 源码
Open POSIX Test SuitePOSIX 接口(历史项目)

14.2 LTP (Linux Test Project)

14.2.1 安装与运行

# Ubuntu/Debian 安装
sudo apt install ltp

# 或从源码编译
git clone https://github.com/linux-test-project/ltp.git
cd ltp
make autotools
./configure
make -j$(nproc)
sudo make install

# 运行所有合规测试
cd /opt/ltp
sudo ./runltp

# 运行特定 POSIX 接口测试
sudo ./runltp -f syscalls
sudo ./runltp -f pty
sudo ./runltp -f timers

# 运行单个测试用例
./testcases/bin/fork01
./testcases/bin/pipe01

14.2.2 LTP 测试结果解读

<<<test_output>>>
tst_test.c:1365: INFO: Timeout per run is 0h 05m 00s
fork01      0  TINFO  :  Test fork01
fork01      1  TPASS  :  fork() works correctly  ← 测试通过
fork01      0  TINFO  :  Cleaning up
<<<execution_status>>>
initiation_status="ok"
duration=0 termination_type=exited termination_id=0 corefile=no
结果含义
TPASS测试通过
TFAIL测试失败
TBROK测试中断(环境问题)
TCONF不适用(功能未配置)
TWARN警告

14.3 自建 POSIX 合规测试框架

14.3.1 轻量级测试框架

/*
 * posix_test.h - 轻量级 POSIX 合规测试框架
 */
#ifndef POSIX_TEST_H
#define POSIX_TEST_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static int _test_pass = 0;
static int _test_fail = 0;
static int _test_total = 0;

#define TEST_BEGIN(name) \
    do { \
        printf("[TEST] %s\n", name); \
    } while (0)

#define TEST_END() \
    do { \
        printf("  结果: %d 通过, %d 失败 (共 %d)\n\n", \
               _test_pass, _test_fail, _test_total); \
    } while (0)

#define ASSERT(expr) \
    do { \
        _test_total++; \
        if (!(expr)) { \
            printf("  FAIL [%s:%d] %s\n", __FILE__, __LINE__, #expr); \
            _test_fail++; \
        } else { \
            printf("  PASS [%s:%d] %s\n", __FILE__, __LINE__, #expr); \
            _test_pass++; \
        } \
    } while (0)

#define ASSERT_EQ(a, b) \
    do { \
        _test_total++; \
        long long _a = (long long)(a); \
        long long _b = (long long)(b); \
        if (_a != _b) { \
            printf("  FAIL [%s:%d] %s == %s (%lld != %lld)\n", \
                   __FILE__, __LINE__, #a, #b, _a, _b); \
            _test_fail++; \
        } else { \
            printf("  PASS [%s:%d] %s == %s\n", __FILE__, __LINE__, #a, #b); \
            _test_pass++; \
        } \
    } while (0)

#define ASSERT_STR_EQ(a, b) \
    do { \
        _test_total++; \
        if (strcmp((a), (b)) != 0) { \
            printf("  FAIL [%s:%d] \"%s\" != \"%s\"\n", \
                   __FILE__, __LINE__, (a), (b)); \
            _test_fail++; \
        } else { \
            printf("  PASS [%s:%d] strings equal\n", __FILE__, __LINE__); \
            _test_pass++; \
        } \
    } while (0)

#define ASSERT_ERRNO(expected) \
    do { \
        _test_total++; \
        if (errno != (expected)) { \
            printf("  FAIL [%s:%d] errno=%d, expected=%d (%s)\n", \
                   __FILE__, __LINE__, errno, (expected), strerror(errno)); \
            _test_fail++; \
        } else { \
            printf("  PASS [%s:%d] errno=%d\n", __FILE__, __LINE__, errno); \
            _test_pass++; \
        } \
    } while (0)

#define TEST_SUMMARY() \
    do { \
        printf("=== 测试汇总 ===\n"); \
        printf("通过: %d, 失败: %d, 总计: %d\n", \
               _test_pass, _test_fail, _test_total); \
        return _test_fail > 0 ? 1 : 0; \
    } while (0)

#endif /* POSIX_TEST_H */

14.3.2 使用测试框架

/*
 * test_posix_basics.c - POSIX 基础接口合规测试
 * 编译: gcc -Wall -o test_posix_basics test_posix_basics.c
 */
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include "posix_test.h"

static void test_file_operations(void)
{
    TEST_BEGIN("文件操作 (open/read/write/close)");

    const char *path = "/tmp/posix_test_file";

    /* 测试 open + O_CREAT */
    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    ASSERT(fd >= 0);

    /* 测试 write */
    const char *data = "POSIX test data";
    ssize_t n = write(fd, data, strlen(data));
    ASSERT_EQ(n, (long long)strlen(data));

    close(fd);

    /* 测试 read */
    fd = open(path, O_RDONLY);
    ASSERT(fd >= 0);

    char buf[64];
    n = read(fd, buf, sizeof(buf) - 1);
    ASSERT_EQ(n, (long long)strlen(data));
    buf[n] = '\0';
    ASSERT_STR_EQ(buf, data);

    close(fd);

    /* 测试 stat */
    struct stat st;
    ASSERT_EQ(stat(path, &st), 0);
    ASSERT(S_ISREG(st.st_mode));
    ASSERT_EQ(st.st_size, (long long)strlen(data));

    /* 测试 unlink */
    ASSERT_EQ(unlink(path), 0);

    /* 测试 open 不存在的文件 */
    fd = open("/tmp/nonexistent_file_xyz", O_RDONLY);
    ASSERT_EQ(fd, -1);
    ASSERT_ERRNO(ENOENT);

    TEST_END();
}

static void test_pipe(void)
{
    TEST_BEGIN("管道 (pipe)");

    int pipefd[2];
    ASSERT_EQ(pipe(pipefd), 0);

    /* 测试写入和读取 */
    const char *msg = "pipe test";
    ssize_t n = write(pipefd[1], msg, strlen(msg));
    ASSERT_EQ(n, (long long)strlen(msg));

    char buf[64];
    n = read(pipefd[0], buf, sizeof(buf) - 1);
    ASSERT_EQ(n, (long long)strlen(msg));
    buf[n] = '\0';
    ASSERT_STR_EQ(buf, msg);

    /* 测试空管道非阻塞读取 */
    int flags = fcntl(pipefd[0], F_GETFL);
    fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
    n = read(pipefd[0], buf, sizeof(buf));
    ASSERT_EQ(n, -1);
    ASSERT(errno == EAGAIN || errno == EWOULDBLOCK);

    close(pipefd[0]);
    close(pipefd[1]);

    TEST_END();
}

static void test_fork_wait(void)
{
    TEST_BEGIN("fork/wait");

    pid_t pid = fork();
    ASSERT(pid >= 0);

    if (pid == 0) {
        /* 子进程 */
        _exit(42);
    } else {
        /* 父进程 */
        int status;
        pid_t ret = waitpid(pid, &status, 0);
        ASSERT_EQ(ret, pid);
        ASSERT(WIFEXITED(status));
        ASSERT_EQ(WEXITSTATUS(status), 42);
    }

    TEST_END();
}

static void test_signals(void)
{
    TEST_BEGIN("信号 (sigaction)");

    volatile sig_atomic_t received = 0;

    struct sigaction sa = {
        .sa_handler = (void (*)(int))(void (*)(void))&received,
        .sa_flags = 0,
    };
    /* 简单信号处理器 */
    static volatile sig_atomic_t sig_count = 0;
    sa.sa_handler = [](int s) { (void)s; sig_count++; };
    sigemptyset(&sa.sa_mask);

    struct sigaction old_sa;
    ASSERT_EQ(sigaction(SIGUSR1, &sa, &old_sa), 0);

    /* 发送信号给自己 */
    ASSERT_EQ(raise(SIGUSR1), 0);
    ASSERT_EQ(sig_count, 1);

    /* 恢复旧处理 */
    sigaction(SIGUSR1, &old_sa, NULL);

    (void)received;
    TEST_END();
}

static void test_time(void)
{
    TEST_BEGIN("时间函数");

    struct timespec ts;
    ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &ts), 0);
    ASSERT(ts.tv_sec > 0);

    /* 测试 clock_getres */
    struct timespec res;
    ASSERT_EQ(clock_getres(CLOCK_MONOTONIC, &res), 0);
    ASSERT(res.tv_nsec > 0 || res.tv_sec > 0);

    /* 测试 nanosleep */
    struct timespec req = { .tv_sec = 0, .tv_nsec = 1000000 }; /* 1ms */
    ASSERT_EQ(nanosleep(&req, NULL), 0);

    TEST_END();
}

static void test_env(void)
{
    TEST_BEGIN("环境变量");

    /* 设置 */
    ASSERT_EQ(setenv("_POSIX_TEST_VAR", "test_value", 1), 0);

    /* 获取 */
    char *val = getenv("_POSIX_TEST_VAR");
    ASSERT(val != NULL);
    ASSERT_STR_EQ(val, "test_value");

    /* 删除 */
    ASSERT_EQ(unsetenv("_POSIX_TEST_VAR"), 0);
    val = getenv("_POSIX_TEST_VAR");
    ASSERT(val == NULL);

    TEST_END();
}

static void test_sysconf(void)
{
    TEST_BEGIN("sysconf 系统配置");

    long pagesize = sysconf(_SC_PAGESIZE);
    ASSERT(pagesize > 0);
    ASSERT_EQ(pagesize & (pagesize - 1), 0);  /* 应为 2 的幂 */

    long ncpu = sysconf(_SC_NPROCESSORS_ONLN);
    ASSERT(ncpu > 0);

    long open_max = sysconf(_SC_OPEN_MAX);
    ASSERT(open_max > 0);

    TEST_END();
}

int main(void)
{
    printf("POSIX 合规测试套件\n");
    printf("==================\n\n");

    test_file_operations();
    test_pipe();
    test_fork_wait();
    test_signals();
    test_time();
    test_env();
    test_sysconf();

    TEST_SUMMARY();
}

14.3.3 修复信号测试

上面的 test_signals 使用了 C++ lambda 风格,改为标准 C:

/* 正确的信号测试(标准 C) */
static volatile sig_atomic_t g_sig_count = 0;

static void test_sig_handler(int sig)
{
    (void)sig;
    g_sig_count++;
}

static void test_signals(void)
{
    TEST_BEGIN("信号 (sigaction)");

    struct sigaction sa = {
        .sa_handler = test_sig_handler,
        .sa_flags = 0,
    };
    sigemptyset(&sa.sa_mask);

    struct sigaction old_sa;
    ASSERT_EQ(sigaction(SIGUSR1, &sa, &old_sa), 0);

    g_sig_count = 0;
    ASSERT_EQ(raise(SIGUSR1), 0);
    ASSERT_EQ(g_sig_count, 1);

    sigaction(SIGUSR1, &old_sa, NULL);
    TEST_END();
}

14.4 函数级合规测试

14.4.1 测试边界条件

/*
 * test_edge_cases.c - POSIX 函数边界条件测试
 * 编译: gcc -Wall -o test_edge_cases test_edge_cases.c
 */
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

static int tests_passed = 0;
static int tests_failed = 0;

#define CHECK(expr, desc) do { \
    if (expr) { printf("  PASS: %s\n", desc); tests_passed++; } \
    else      { printf("  FAIL: %s\n", desc); tests_failed++; } \
} while(0)

static void test_pipe_limits(void)
{
    printf("[pipe 限制]\n");

    int fd[2];
    CHECK(pipe(fd) == 0, "pipe() 创建成功");

    /* 测试 PIPE_BUF 原子写入 */
    long pipe_buf = fpathconf(fd[0], _PC_PIPE_BUF);
    CHECK(pipe_buf > 0, "PIPE_BUF 可查询");
    printf("  PIPE_BUF = %ld\n", pipe_buf);

    /* 写入 PIPE_BUF 字节应该原子完成 */
    char *buf = malloc(pipe_buf);
    if (buf) {
        memset(buf, 'A', pipe_buf);
        /* 非阻塞写入 */
        int flags = fcntl(fd[1], F_GETFL);
        fcntl(fd[1], F_SETFL, flags | O_NONBLOCK);
        ssize_t n = write(fd[1], buf, pipe_buf);
        CHECK(n == pipe_buf, "PIPE_BUF 字节写入原子完成");
        free(buf);
    }

    close(fd[0]);
    close(fd[1]);
}

static void test_path_limits(void)
{
    printf("\n[路径限制]\n");

    long path_max = pathconf("/", _PC_PATH_MAX);
    CHECK(path_max > 0, "PATH_MAX 可查询");
    printf("  PATH_MAX = %ld\n", path_max);

    long name_max = pathconf("/", _PC_NAME_MAX);
    CHECK(name_max > 0, "NAME_MAX 可查询");
    printf("  NAME_MAX = %ld\n", name_max);
}

static void test_fd_limits(void)
{
    printf("\n[文件描述符限制]\n");

    long open_max = sysconf(_SC_OPEN_MAX);
    CHECK(open_max > 0, "OPEN_MAX 可查询");
    printf("  OPEN_MAX = %ld\n", open_max);

    /* 测试 dup2 */
    int fd = open("/dev/null", 0);
    if (fd >= 0) {
        int newfd = dup2(fd, 100);
        CHECK(newfd == 100, "dup2 到指定 fd 成功");
        CHECK(close(newfd) == 0, "关闭 dup 的 fd");
        CHECK(close(fd) == 0, "关闭原始 fd");
    }
}

static void test_string_functions(void)
{
    printf("\n[字符串函数]\n");

    /* strlen */
    CHECK(strlen("") == 0, "strlen(\"\") == 0");
    CHECK(strlen("hello") == 5, "strlen(\"hello\") == 5");

    /* strcmp */
    CHECK(strcmp("abc", "abc") == 0, "strcmp 相等");
    CHECK(strcmp("abc", "abd") < 0, "strcmp 小于");
    CHECK(strcmp("abd", "abc") > 0, "strcmp 大于");

    /* strncpy */
    char dst[10];
    strncpy(dst, "hello", sizeof(dst));
    CHECK(strcmp(dst, "hello") == 0, "strncpy 正常复制");

    /* snprintf */
    char buf[64];
    int n = snprintf(buf, sizeof(buf), "value=%d", 42);
    CHECK(strcmp(buf, "value=42") == 0, "snprintf 格式化正确");
    CHECK(n == 8, "snprintf 返回写入长度");
}

int main(void)
{
    printf("POSIX 边界条件测试\n");
    printf("==================\n\n");

    test_pipe_limits();
    test_path_limits();
    test_fd_limits();
    test_string_functions();

    printf("\n=== 结果: %d 通过, %d 失败 ===\n",
           tests_passed, tests_failed);

    return tests_failed > 0 ? 1 : 0;
}

14.5 运行时兼容性检查

/*
 * compat_check.c - 运行时 POSIX 兼容性检查
 * 编译: gcc -Wall -o compat_check compat_check.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h>

typedef struct {
    const char *name;
    int sc_name;
    long min_value;
} check_t;

int main(void)
{
    struct utsname uts;
    uname(&uts);
    printf("系统: %s %s\n", uts.sysname, uts.release);
    printf("架构: %s\n\n", uts.machine);

    check_t checks[] = {
        {"页面大小", _SC_PAGESIZE, 1},
        {"最大打开文件数", _SC_OPEN_MAX, 64},
        {"路径最大长度", _PC_PATH_MAX, 256},
        {"文件名最大长度", _PC_NAME_MAX, 14},
        {"管道缓冲区", _PC_PIPE_BUF, 512},
        {"时钟滴答", _SC_CLK_TCK, 1},
        {"参数最大长度", _SC_ARG_MAX, 4096},
    };
    int nchecks = sizeof(checks) / sizeof(checks[0]);

    int issues = 0;
    for (int i = 0; i < nchecks; i++) {
        long val;
        /* _PC_* 使用 pathconf,_SC_* 使用 sysconf */
        if (checks[i].sc_name >= 0 && checks[i].sc_name < 1000)
            val = sysconf(checks[i].sc_name);
        else
            val = pathconf("/", checks[i].sc_name);

        if (val < 0) {
            printf("  %-25s: 不支持\n", checks[i].name);
        } else if (val < checks[i].min_value) {
            printf("  %-25s: %ld (最小要求: %ld) ⚠️\n",
                   checks[i].name, val, checks[i].min_value);
            issues++;
        } else {
            printf("  %-25s: %ld ✓\n", checks[i].name, val);
        }
    }

    /* 检查 POSIX 版本 */
    printf("\n");
#ifdef _POSIX_VERSION
    printf("  POSIX 版本: %ldL", (long)_POSIX_VERSION);
    if (_POSIX_VERSION >= 200809L)
        printf(" ✓ (>= 200809L)\n");
    else
        printf(" ⚠️ (需要 >= 200809L)\n");
#else
    printf("  POSIX 版本: 未定义 ⚠️\n");
    issues++;
#endif

    printf("\n兼容性检查: %s (%d 个问题)\n",
           issues == 0 ? "通过" : "有问题", issues);

    return issues > 0 ? 1 : 0;
}

14.6 持续集成 (CI) 中的 POSIX 测试

14.6.1 GitHub Actions 示例

# .github/workflows/posix-test.yml
name: POSIX Compliance Tests

on: [push, pull_request]

jobs:
  test-linux:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: sudo apt-get install -y build-essential
      - name: Build
        run: make CC=gcc CFLAGS="-Wall -Wextra -D_POSIX_C_SOURCE=200809L"
      - name: Run tests
        run: make test

  test-macos:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: make CC=clang CFLAGS="-Wall -Wextra -D_POSIX_C_SOURCE=200809L"
      - name: Run tests
        run: make test

  test-freebsd:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: FreeBSD build
        uses: vmactions/freebsd-vm@v1
        with:
          prepare: pkg install -y gmake
          run: gmake test

14.6.2 测试 Makefile

CC ?= gcc
CFLAGS ?= -Wall -Wextra -D_POSIX_C_SOURCE=200809L
LDLIBS ?= -lpthread

TESTS = test_posix_basics test_edge_cases compat_check

.PHONY: all test clean

all: $(TESTS)

test: $(TESTS)
	@echo "=== 运行 POSIX 合规测试 ==="
	@failed=0; \
	for t in $(TESTS); do \
		echo "--- $$t ---"; \
		if ./$$t; then \
			echo "$$t: PASS"; \
		else \
			echo "$$t: FAIL"; \
			failed=$$((failed + 1)); \
		fi; \
		echo; \
	done; \
	if [ $$failed -gt 0 ]; then \
		echo "$$failed 个测试失败"; \
		exit 1; \
	fi; \
	echo "所有测试通过"

clean:
	rm -f $(TESTS) *.o

14.7 注意事项

⚠️ 测试隔离:每个测试应独立运行,不依赖其他测试的副作用。使用临时文件并在测试后清理。

⚠️ 权限问题:某些 POSIX 测试需要特定权限(如 mlock 需要 CAP_IPC_LOCK,信号测试需要用户权限)。在 CI 中配置适当权限。

⚠️ 容器环境:Docker 容器中某些测试可能不适用(如 CLOCK_REALTIME 相关、网络测试)。使用 TCONF 标记跳过。

⚠️ 编译器警告:使用 -Wall -Wextra -Werror 编译测试代码,确保无警告。

⚠️ 测试覆盖:重点测试边界条件和错误路径(errno 值),而非正常路径。


14.8 扩展阅读

  1. LTP 官网https://linux-test-project.github.io/
  2. Open Group 认证测试https://www.opengroup.org/certification
  3. Linux Kernel Selfteststools/testing/selftests/ 目录
  4. glibc 测试指南:glibc 源码中 INSTALL 文件
  5. 《Test Driven Development》 — Kent Beck 著

14.9 本章小结

要点说明
LTPLinux 内核和系统调用的标准测试套件
自建测试框架使用宏定义轻量级 assert 测试
边界条件重点测试返回值、errno、极端输入
运行时检查sysconf/pathconf 验证系统能力
CI 集成Linux + macOS + FreeBSD 多平台自动测试
测试隔离每个测试独立,使用临时文件