qemu单元测试glib的测试框架,其使用可以参考:https://developer.gnome.org/glib/2.37/glib-Testing.html
对于一个用户来说使用这个框架比较简单,下面是一个arm下的tmp105的测试用例:
int main(int argc, char **argv)
{
QTestState *s = NULL;
int ret;
g_test_init(&argc, &argv, NULL);
s = qtest_start("-display none -machine n800");
i2c = omap_i2c_create(OMAP2_I2C_1_BASE);
addr = N8X0_ADDR;
qtest_add_func("/tmp105/tx-rx", send_and_receive);
ret = g_test_run();
if (s) {
qtest_quit(s);
}
g_free(i2c);
return ret;
}
主要步骤是: 调用g_test_init初始化测试框架, 调用qtest_start启动测试的单板, 创建测试用例并添加到测试框架中,执行测试用例, 退出测试等。
这里面主要的函数是qtest _start,它主要功能是创建qemu的测试环境。这里qemu的执行和测试用例是分开的,两个进程通过socket进行通信。测试进程是客户端,qemu进程是服务器端。在tests/libtest.c:
QTestState *qtest_init(const char *extra_args)
{
QTestState *s;
int sock, qmpsock, i;
gchar *pid_file;
gchar *command;
const char *qemu_binary;
pid_t pid;
qemu_binary = getenv("QTEST_QEMU_BINARY");
g_assert(qemu_binary != NULL);
s = g_malloc(sizeof(*s));
s->socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
s->qmp_socket_path = g_strdup_printf("/tmp/qtest-%d.qmp", getpid());
pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid());
sock = init_socket(s->socket_path);
qmpsock = init_socket(s->qmp_socket_path);
pid = fork();
if (pid == 0) {
command = g_strdup_printf("%s "
"-qtest unix:%s,nowait "
"-qtest-log /dev/null "
"-qmp unix:%s,nowait "
"-pidfile %s "
"-machine accel=qtest "
"%s", qemu_binary, s->socket_path,
s->qmp_socket_path, pid_file,
extra_args ?: "");
execlp("/bin/sh", "sh", "-c", command, NULL);
exit(1);
}
s->fd = socket_accept(sock);
s->qmp_fd = socket_accept(qmpsock);
s->rx = g_string_new("");
s->pid_file = pid_file;
s->child_pid = pid;
for (i = 0; i < MAX_IRQ; i++) {
s->irq_level[i] = false;
}
/* Read the QMP greeting and then do the handshake */
qtest_qmp(s, "");
qtest_qmp(s, "{ 'execute': 'qmp_capabilities' }");
if (getenv("QTEST_STOP")) {
kill(qtest_qemu_pid(s), SIGSTOP);
}
return s;
}
这个函数创建了qemu的执行进程,在qemu单元测试的情况下使用的的accel是qtest方式,因为在这个模式下quest并不需要执行指令,但是需要创建整个单板以方便测试。
以一个最简单的writew为例,执行流程如下: 首先测试单元执行这个指令:
static void omap_i2c_set_slave_addr(OMAPI2C *s, uint8_t addr)
{
uint16_t data = addr;
writew(s->addr + OMAP_I2C_SA, data);
data = readw(s->addr + OMAP_I2C_SA);
g_assert_cmphex(data, ==, addr);
}
writew最终会调用qtest_writew:
void qtest_writew(QTestState *s, uint64_t addr, uint16_t value)
{
qtest_write(s, "writew", addr, value);
}
static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
socket_sendf(s->fd, fmt, ap);
va_end(ap);
}
qtest_sendf通过socket.c将cmd以及相关的参数传送给qemu进程
} else if (strcmp(words[0], "writeb") == 0 ||
strcmp(words[0], "writew") == 0 ||
strcmp(words[0], "writel") == 0 ||
strcmp(words[0], "writeq") == 0) {
uint64_t addr;
uint64_t value;
g_assert(words[1] && words[2]);
addr = strtoull(words[1], NULL, 0);
value = strtoull(words[2], NULL, 0);
if (words[0][5] == 'b') {
uint8_t data = value;
cpu_physical_memory_write(addr, &data, 1);
} else if (words[0][5] == 'w') {
uint16_t data = value;
tswap16s(&data);
cpu_physical_memory_write(addr, &data, 2);
} else if (words[0][5] == 'l') {
uint32_t data = value;
tswap32s(&data);
cpu_physical_memory_write(addr, &data, 4);
} else if (words[0][5] == 'q') {
uint64_t data = value;
tswap64s(&data);
cpu_physical_memory_write(addr, &data, 8);
}
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
对write的处理是解析出addr,data,len的参数,然后调用cpu_phsical_memory_write完成这个操作
int qtest_init(void)
{
CharDriverState *chr;
g_assert(qtest_chrdev != NULL);
configure_icount("0");
chr = qemu_chr_new("qtest", qtest_chrdev, NULL);
qemu_chr_add_handlers(chr, qtest_can_read, qtest_read, qtest_event, chr);
qemu_chr_fe_set_echo(chr, true);
inbuf = g_string_new("");
if (qtest_log) {
if (strcmp(qtest_log, "none") != 0) {
qtest_log_fp = fopen(qtest_log, "w+");
}
} else {
qtest_log_fp = stderr;
}
qtest_chr = chr;
return 0;
}
在测试模式下qemu不会执行任何指令, 只会进行简单的处理。在cpus.c里面qemu初始化的时候选择qemu_dummy_cpu_thread_fn
static void *qemu_dummy_cpu_thread_fn(void *arg)
{
#ifdef _WIN32
fprintf(stderr, "qtest is not supported under Windows\n");
exit(1);
#else
CPUState *cpu = arg;
sigset_t waitset;
int r;
qemu_mutex_lock_iothread();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
sigemptyset(&waitset);
sigaddset(&waitset, SIG_IPI);
/* signal CPU creation */
cpu->created = true;
qemu_cond_signal(&qemu_cpu_cond);
cpu_single_env = cpu->env_ptr;
while (1) {
cpu_single_env = NULL;
qemu_mutex_unlock_iothread();
do {
int sig;
r = sigwait(&waitset, &sig);
} while (r == -1 && (errno == EAGAIN || errno == EINTR));
if (r == -1) {
perror("sigwait");
exit(1);
}
qemu_mutex_lock_iothread();
cpu_single_env = cpu->env_ptr;
qemu_wait_io_event_common(cpu);
}