Run a single test rather than make check

Run a single test:

pip3 install meson
cd build
meson test -t 0 qtest-x86_64/qos-test

-t 0 的原因是默认的 timeout 时间是 60s,有时候并不够 qos-test 完成,-t 表示的是 Define a multiplier for test timeout, for example when running tests in particular conditions they might take more time to execute. (<= 0 to disable timeout)。

Features/QTest - QEMU

How to run a specific test in qos-test

All sub-tests in qos-test are registered by function qos_add_test(). 如果我们想要只跑一个 subtest,一个方式是把其他的 qos_add_test() 都注释掉,然后只留下我们想跑的那一个 qos_add_test()。但这种方式工作量是有一点大的。另一种方式是在文件 tests/qtest/qos-test.c 中,我们 apply 下面的 diff 上去(将 strcmp 里的 path 替换成为你希望 test 的 path):

diff --git a/tests/data/acpi/q35/DMAR.dmar b/tests/data/acpi/q35/DMAR.dmar
index 0dca6e68ad..49fd3deb90 100644
Binary files a/tests/data/acpi/q35/DMAR.dmar and b/tests/data/acpi/q35/DMAR.dmar differ
diff --git a/tests/qtest/qos-test.c b/tests/qtest/qos-test.c
index 5da4091ec3..ecb8f3e161 100644
--- a/tests/qtest/qos-test.c
+++ b/tests/qtest/qos-test.c
@@ -292,13 +292,15 @@ static void walk_path(QOSGraphNode *orig_path, int len)
     path_vec[1] = path_vec[0];
     path_vec[0] = g_string_free(cmd_line, false);

-    if (path->u.test.subprocess) {
-        gchar *subprocess_path = g_strdup_printf("/%s/%s/subprocess",
-                                                 qtest_get_arch(), path_str);
-        qtest_add_data_func(path_str, subprocess_path, subprocess_run_one_test);
-        g_test_add_data_func(subprocess_path, path_vec, run_one_test);
-    } else {
-        qtest_add_data_func(path_str, path_vec, run_one_test);
+    if (!strcmp(path_str, "pc/i440FX-pcihost/pci-bus-pc/pci-bus/virtio-net-pci/virtio-net/virtio-net-tests/vhost-user/migrate")) {
+        if (path->u.test.subprocess) {
+            gchar *subprocess_path = g_strdup_printf("/%s/%s/subprocess",
+                                                     qtest_get_arch(), path_str);
+            qtest_add_data_func(path_str, subprocess_path, subprocess_run_one_test);
+            g_test_add_data_func(subprocess_path, path_vec, run_one_test);
+        } else {
+            qtest_add_data_func(path_str, path_vec, run_one_test);
+        }
     }

     g_free(path_str);

libqtest.c

struct QTestState QEMU

这应该就表示一个 QEMU 进程,或者说一个 VM。

struct QTestState
{
    // 网络的 fd,应该是迁移相关的。
    int fd;
    // 通过这个 fd 来执行 qmp 命令
    int qmp_fd;
    // qemu 进程的 pid
    pid_t qemu_pid;  /* our child QEMU process */
    int wstatus;
    //...
    int expected_status;
    bool big_endian;
    bool irq_level[MAX_IRQ];
    GString *rx;
    QTestTransportOps ops;
    // 这是一个 list,里面的每一个元素表示一个 event,是 QDict 类型的
    GList *pending_events;
    QTestQMPEventCallback eventCB;
    void *eventData;
};

qtest_init() QEMU

QTestState *qtest_init(const char *extra_args)
{
    QTestState *s = qtest_init_without_qmp_handshake(extra_args);
    QDict *greeting;

    // Read the QMP greeting and then do the handshake
    greeting = qtest_qmp_receive(s);
    qobject_unref(greeting);
    qobject_unref(qtest_qmp(s, "{ 'execute': 'qmp_capabilities' }"));

    return s;
}

qtest_memwrite() QEMU

void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
{
    const uint8_t *ptr = data;
    size_t i;
    char *enc;

    //...
    enc = g_malloc(2 * size + 1);

    for (i = 0; i < size; i++) {
        sprintf(&enc[i * 2], "%02x", ptr[i]);
    }

    // 先发送 command
    qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x%s\n", addr, size, enc);
    // 处理 command
    qtest_rsp(s);
    g_free(enc);
}

qtest_rsp() QEMU

好像没做什么,就是返回了 command 的 word。

static void qtest_rsp(QTestState *s)
{
    gchar **words = qtest_rsp_args(s, 0);

    g_strfreev(words);
}

QTest command send and receive

struct QTestClientTransportOps QEMU

typedef struct QTestClientTransportOps {
    // qtest_sendf / qtest_bufwrite
    QTestSendFn     send;      /* for sending qtest commands */

    /*
     * use external_send to send qtest command strings through functions which
     * do not accept a QTestState as the first parameter.
     */
    // 
    ExternalSendFn  external_send;

    // qtest_rsp_args() // 这个函数中的 rsp 叫做 response
    //     line = s->ops.recv_line(s);
    QTestRecvFn     recv_line; /* for receiving qtest command responses */
} QTestTransportOps;

可以看到发送和接收的函数都是传进来的函数。

static void qtest_client_set_tx_handler(QTestState *s, QTestSendFn send)
{
    s->ops.send = send;
}
static void qtest_client_set_rx_handler(QTestState *s, QTestRecvFn recv)
{
    s->ops.recv_line = recv;
}

发送和接收有两种方式:process 内部直接发送(inproc)和通过 socket。

qtest_client_inproc_recv_line() / send_wrapper() / qtest_server_inproc_recv() QEMU

Send 函数是 qtest_server_inproc_recv(),receive 函数是 qtest_client_inproc_recv_line()

// tests/qtest/fuzz/fuzz.c
qtest_setup
    qtest_inproc_init(&fuzz_qts, false, fuzz_arch, &qtest_server_inproc_recv);
        qts->ops.external_send = send;
        qtest_client_set_tx_handler(qts, send_wrapper);

// 可见虽然叫做 recv,其实是 send 函数
// 可能是因为 send 之后就直接开始 receive 然后开始处理了
void qtest_server_inproc_recv(void *dummy, const char *buf)
{
    // 这是一个 static 的,说明每次调用都会往后面 append string
    static GString *gstr;
    if (!gstr) {
        gstr = g_string_new(NULL);
    }
    // 再 append 一些 string,直到最后一个字符是 '\n'
    g_string_append(gstr, buf);
    // 如果最后一个字符是 '\n'
    if (gstr->str[gstr->len - 1] == '\n') {
        qtest_process_inbuf(NULL, gstr);
        g_string_truncate(gstr, 0);
    }
}
qtest_setup
    qtest_inproc_init
        qtest_client_set_rx_handler(qts, qtest_client_inproc_recv_line);
// 这个函数基于的还是 s->rx-str 这个 string
static GString *qtest_client_inproc_recv_line(QTestState *s)
{
    GString *line;
    size_t offset;
    char *eol;

    eol = strchr(s->rx->str, '\n');
    offset = eol - s->rx->str;
    // 从 '\n' 处截取一个 command line
    line = g_string_new_len(s->rx->str, offset);
    // 把截取之前的部分清空掉
    g_string_erase(s->rx, 0, offset + 1);
    return line;
}

qtest_process_inbuf() / qtest_process_command() QEMU

static void qtest_process_inbuf(CharBackend *chr, GString *inbuf)
{
    char *end;

    // Returns a pointer to the first occurrence of the character '\n'
    while ((end = strchr(inbuf->str, '\n')) != NULL) {
        size_t offset;
        GString *cmd;
        gchar **words;

        offset = end - inbuf->str;

        // cmd 是一个 string,通过这个 offset 得到
        cmd = g_string_new_len(inbuf->str, offset);
        // 清空 [0, offset] 内容
        g_string_erase(inbuf, 0, offset + 1);

        // 把这个 command 按照空格分成不同的词
        words = g_strsplit(cmd->str, " ", 0);
        qtest_process_command(chr, words);
        //...
    }
}

qtest_add_data_func_full() QEMU

void qtest_add_data_func_full(const char *str, void *data,
                              void (*fn)(const void *),
                              GDestroyNotify data_free_func)
{
    gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str);
    g_test_add_data_func_full(path, data, fn, data_free_func);
    g_free(path);
}