> For the complete documentation index, see [llms.txt](https://richardweiyang-2.gitbook.io/kernel-exploring/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://richardweiyang-2.gitbook.io/kernel-exploring/handy_tools/03-selftest/03_01-build.md).

# 构建过程

kselftest的源代码在tools/testing/selftests目录下面，不同的测试大类按照目录区分。比如测试cgroup的就在cgroup目录下，测试mm的就在mm目录下。

每个大类都可以单独构建，比如想要测试mm的内容，就可以直接进入到mm目录，运行make。

```
cd linux
cd tools/testing/selftests
cd mm
make
```

下面我们按照自下而上的顺序，先了解如何构建独立的测试用例，再来看如何构建所有的测试用例。

## kselftest\_harness/Makefile

我们看selftests中kselftest\_harness这个目录下的测试用例，这个是自己测试自己的用例。

文件不多，代码不长。用来学习selftest是很不错的入口。

其中关键的文件是两个

```
Makefile
harness-selftest.c
```

也就是说新增一个测试用例，最少需要两个文件：一个源文件，一个makefile。

源文件的内容我们放到以后来分析，这里主要分析makefile的过程。

打开这个makefile，发现内容非常少。但是最后又包含了lib.mk。所以重要的工作实际放在了这个库中。

```
TEST_GEN_PROGS_EXTENDED := harness-selftest
TEST_PROGS := harness-selftest.sh
EXTRA_CLEAN := harness-selftest.seen

include ../lib.mk
```

## lib.mk

从上面makefile的写法来看，说明lib.mk中定义了几个变量名。将这些变量的内容设定后，就可以利用lib.mk的规则生成想要的目标。

现在我们就打开这个lib.mk文件看看，这些是如何工作的。为我们后续的工作做准备。以下是对lib.mk文件的总体分析：

```
ifneq ($(LLVM),)
...
else
CC := $(CROSS_COMPILE)gcc    # 先设置了要使用的编译器
endif # LLVM

# 如果没有指定的话，当前目录就是目标输出的目录。且是绝对路径
OUTPUT := $(shell pwd)
# 将selfdir设置为lib.mk所在的绝对路径
selfdir = $(realpath $(dir $(filter %/lib.mk,$(MAKEFILE_LIST))))
# 将top_srcdir设置为linux内核源码根目录
top_srcdir = $(selfdir)/../../..


# 这里已经看到了makefile中的一个变量TEST_GEN_PROGS_EXTENDED 
# 就是将目标加上输出目录的路径
TEST_GEN_PROGS := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS))
TEST_GEN_PROGS_EXTENDED := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS_EXTENDED))
TEST_GEN_FILES := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_FILES))


# 这里是整个构建过程的目标，可以看到主要就是上面三个变量指定的
all: $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) \
	$(if $(TEST_GEN_MODS_DIR),gen_mods_dir)


# 接下来文件中定义了一些target，如install, run_tests。这些我们先跳过
...

# 构建OUTPUT的规则, 使用者还能定义OVERRIDE_TARGETS来覆盖规则
ifeq ($(OVERRIDE_TARGETS),)
LOCAL_HDRS += $(selfdir)/kselftest_harness.h $(selfdir)/kselftest.h
$(OUTPUT)/%:%.c $(LOCAL_HDRS)
	$(call msg,CC,,$@)
	$(Q)$(LINK.c) $(filter-out $(LOCAL_HDRS),$^) $(LDLIBS) -o $@

$(OUTPUT)/%.o:%.S
	$(COMPILE.S) $^ -o $@

$(OUTPUT)/%:%.S
	$(LINK.S) $^ $(LDLIBS) -o $@
endif
```

### 显示出构建具体命令

在lib.mk中，有一段定义了Q/msg命令。通常来说，构建过程会隐藏构建目标时具体使用的命令。

但是有时候我们需要知道构建时究竟用了什么参数，包含头文件的路径。所以希望能展示出详细的命令行。

根据lib.mk中的定义，我们执行时加上V=1就可以了。如

```
$make V=1
gcc -D_GNU_SOURCE=     harness-selftest.c  -o /home/richard/git/linux/tools/testing/selftests/kselftest_harness/harness-selftest
```

### 运行测试用例

在测试用例目录下，如tools/testing/selftests/kselftest\_harness，执行make run\_tests就可以运行当前目录下的测试用例。

这个目标在lib.mk中。

```
define RUN_TESTS
	BASE_DIR="$(selfdir)";			\
	. $(selfdir)/kselftest/runner.sh;	\
	echo $(1);				\
	if [ "X$(summary)" != "X" ]; then       \
		per_test_logging=1;		\
	fi;                                     \
	run_many $(1)
endef

run_tests: all
	@$(call RUN_TESTS, $(TEST_GEN_PROGS) $(TEST_CUSTOM_PROGS) $(TEST_PROGS))
```

当make run\_tests执行后，就会运行call RUN\_TESTS，而后面的变量都形成一个字符串作为第一个参数传给RUN\_TESTS。

最后运行在kselftest/runner.sh脚本中的run\_many函数执行测试用例，参数也是那串字符串。

```
run_many()
{
	echo "TAP version 13"
	DIR="${PWD#${BASE_DIR}/}"
	test_num=0
	total=$(echo "$@" | wc -w)
	echo "1..$total"
	for TEST in "$@"; do
		BASENAME_TEST=$(basename $TEST)
		test_num=$(( test_num + 1 ))
		if [ -n "$per_test_logging" ]; then
			logfile="/tmp/$BASENAME_TEST"
			cat /dev/null > "$logfile"
		fi
		if [ -n "$RUN_IN_NETNS" ]; then
			run_in_netns &
		else
			run_one "$DIR" "$TEST" "$test_num"
		fi
	done

	wait
}
```

传入的字符串参数，按照空格切分，有几个就表示有多少测试。然后针对每个测试运行run\_one,其中会

* 读取测试配置文件settings
* 设置timeout
* 获取测试命令参数
* 拼接测试命令行 cmd
* 运行拼接好的命令行 cmd

其中在执行最终命令时，用了一段有点复杂的脚本：

```
		((((( tap_timeout "$cmd" 2>&1; echo $? >&3) |
			tap_prefix >&4) 3>&1) |
			(read xs; exit $xs)) 4>>"$logfile" &&
		echo "ok $test_num $TEST_HDR_MSG")
```

这里面可以分成几个部分来看

```
  ( tap_timeout "$cmd" 2>&1; echo $? >&3) 
```

执行$cmd，将错误输出合并到标准输出；然后把退出码写道文件描述符3。

```(((
```

将刚才命令的输出，通过tap\_prefix格式化，再写到文件描述符4。 然后把刚才写道文件描述符3的退出码再写回标准输出。

```
	((() | (read xs; exit $xs)) 4>>"$logfile" && echo "ok $test_num $TEST_HDR_MSG")
```

把写到标准输出的提出码读到变量xs，然后再退出。 同时将刚才写到文件描述符4的命令运行输出内容，写道日志文件logfile。 如果这一切都顺利，也就是exit $xs也是返回0（表示成功），则会提示测试成功 ok 。

## mm/Makefile

对lib.mk有一定了解后，我们再来找一个例子，看看我们对构建的流程是否真的理解。也巩固一下学到的知识。

文件以include ../lib.mk为界。

前面定义了

* TEST\_GEN\_FILES: 这是最终要编译成可执行文件的测试目标
* TEST\_PROGS: 这是make run\_tests最后会执行的脚本

之后定义了

* 目标文件的额外依赖： $(TEST\_GEN\_FILES): vm\_util.c thp\_settings.c
* 链接时额外库： $(OUTPUT)/migration: LDLIBS += -lnuma

这么看，对selftest中构建过程已经有一定了解了。

## Makefile

ok，上面我们了解了单独的一个测试用例是如何构建的，那现在来看看整个selftest的构建过程。这个就要分析tools/testing/selftests/Makefile文件了。

```
# 首先设置了目标，基本就是每个子目录作为一个目标
TARGETS += ...


# 如果在tools/testing/selftests目录下运行make，
# 那么会用下面的方法设置内核源代码的具体对路径
  BUILD := $(CURDIR)
  abs_srctree := $(shell cd $(top_srcdir) && pwd)
  KHDR_INCLUDES := -isystem ${abs_srctree}/usr/include
  DEFAULT_INSTALL_HDR_PATH := 1


all:
	@ret=1;							\
	for TARGET in $(TARGETS) $(INSTALL_DEP_TARGETS); do	\
		BUILD_TARGET=$$BUILD/$$TARGET;			\
		mkdir $$BUILD_TARGET  -p;			\
		$(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET	\
				O=$(abs_objtree)		\
				$(if $(FORCE_TARGETS),|| exit);	\
		ret=$$((ret * $$?));				\
	done; exit $$ret;
```

所以这么看，selftests根目录上的Makefile其实没有做什么事。就是设置好了TARGETS后，针对每个target运行make -C $TARGET。

除了all这个目标，还有run\_tests,hotpulg等其他目标。不过结构都差不多，就不展开了。
