/*
 * Copyright (C) 2011-2013 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 */

#include <config.h>

#include <time.h>

#include "testutils.h"
#include "testutilsqemuschema.h"
#include "qemumonitortestutils.h"

#include "virthread.h"
#define LIBVIRT_QEMU_PROCESSPRIV_H_ALLOW
#include "qemu/qemu_processpriv.h"
#include "qemu/qemu_monitor.h"
#include "qemu/qemu_agent.h"
#include "rpc/virnetsocket.h"
#include "viralloc.h"
#include "virlog.h"
#include "virerror.h"
#include "vireventthread.h"

#define VIR_FROM_THIS VIR_FROM_NONE

VIR_LOG_INIT("tests.qemumonitortestutils");

struct _qemuMonitorTestItem {
    char *identifier;
    qemuMonitorTestResponseCallback cb;
    void *opaque;
    virFreeCallback freecb;
};

struct _qemuMonitorTest {
    virMutex lock;
    virThread thread;

    bool quit;
    bool running;
    bool started;

    bool allowUnusedCommands;

    bool skipValidationDeprecated;
    bool skipValidationRemoved;

    char *incoming;
    size_t incomingLength;
    size_t incomingCapacity;

    char *outgoing;
    size_t outgoingLength;
    size_t outgoingCapacity;

    virNetSocket *server;
    virNetSocket *client;

    virEventThread *eventThread;

    qemuMonitor *mon;
    qemuAgent *agent;

    char *tmpdir;

    size_t nitems;
    qemuMonitorTestItem **items;

    virDomainObj *vm;
    GHashTable *qapischema;
};


static void
qemuMonitorTestItemFree(qemuMonitorTestItem *item)
{
    if (!item)
        return;

    g_free(item->identifier);

    if (item->freecb)
        (item->freecb)(item->opaque);

    g_free(item);
}


/*
 * Appends data for a reply to the outgoing buffer
 */
int
qemuMonitorTestAddResponse(qemuMonitorTest *test,
                           const char *response)
{
    size_t want = strlen(response) + 2;
    size_t have = test->outgoingCapacity - test->outgoingLength;

    VIR_DEBUG("Adding response to monitor command: '%s", response);

    if (have < want) {
        size_t need = want - have;
        VIR_EXPAND_N(test->outgoing, test->outgoingCapacity, need);
    }

    want -= 2;
    memcpy(test->outgoing + test->outgoingLength, response, want);
    memcpy(test->outgoing + test->outgoingLength + want, "\r\n", 2);
    test->outgoingLength += want + 2;
    return 0;
}


static int
qemuMonitorTestAddErrorResponseInternal(qemuMonitorTest *test,
                                        const char *usermsg)
{
    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
    g_autofree char *escapemsg = NULL;
    g_autofree char *jsonmsg = NULL;
    char *tmp;

    if (!usermsg)
        usermsg = "unexpected command";

    virBufferEscape(&buf, '\\', "\"", "%s", usermsg);
    escapemsg = virBufferContentAndReset(&buf);

    /* replace newline/carriage return with space */
    tmp = escapemsg;
    while (*tmp) {
        if (*tmp == '\r' || *tmp == '\n')
            *tmp = ' ';

        tmp++;
    }

    /* format the JSON error message */
    jsonmsg = g_strdup_printf("{ \"error\": " " { \"desc\": \"%s\", "
                              "   \"class\": \"UnexpectedCommand\" } }", escapemsg);

    return qemuMonitorTestAddResponse(test, jsonmsg);
}


int
qemuMonitorTestAddInvalidCommandResponse(qemuMonitorTest *test,
                                         const char *expectedcommand,
                                         const char *actualcommand)
{
    g_autofree char *msg = NULL;

    msg = g_strdup_printf("expected command '%s' got '%s'", expectedcommand,
                          actualcommand);

    return qemuMonitorTestAddErrorResponseInternal(test, msg);
}


int
qemuMonitorTestAddErrorResponse(qemuMonitorTest *test, const char *errmsg, ...)
{
    va_list msgargs;
    g_autofree char *msg = NULL;
    g_autofree char *jsonmsg = NULL;
    int ret = -1;

    va_start(msgargs, errmsg);

    msg = g_strdup_vprintf(errmsg, msgargs);

    jsonmsg = g_strdup_printf("{ \"error\": " " { \"desc\": \"%s\", "
                              "   \"class\": \"UnexpectedCommand\" } }", msg);

    ret = qemuMonitorTestAddResponse(test, jsonmsg);

    va_end(msgargs);
    return ret;
}


static void G_GNUC_NORETURN G_GNUC_PRINTF(1, 2)
qemuMonitorTestError(const char *errmsg,
                     ...)
{
    va_list msgargs;

    va_start(msgargs, errmsg);

    g_fprintf(stderr, "\n");
    g_vfprintf(stderr, errmsg, msgargs);
    g_fprintf(stderr, "\n");
    exit(EXIT_FAILURE); /* exempt from syntax-check */
}


static void G_GNUC_NORETURN
qemuMonitorTestErrorInvalidCommand(const char *expectedcommand,
                                   const char *actualcommand)
{
    qemuMonitorTestError("expected command '%s' got '%s'",
                         expectedcommand, actualcommand);
}



static int
qemuMonitorTestProcessCommand(qemuMonitorTest *test,
                              const char *cmdstr)
{
    int ret;

    VIR_DEBUG("Processing string from monitor handler: '%s", cmdstr);

    if (test->nitems == 0) {
        qemuMonitorTestError("unexpected command: '%s'", cmdstr);
    } else {
        qemuMonitorTestItem *item = test->items[0];
        ret = (item->cb)(test, item, cmdstr);
        qemuMonitorTestItemFree(item);
        if (VIR_DELETE_ELEMENT(test->items, 0, test->nitems) < 0)
            return -1;
    }

    return ret;
}


/*
 * Handles read/write of monitor data on the monitor server side
 */
static void
qemuMonitorTestIO(virNetSocket *sock,
                  int events,
                  void *opaque)
{
    qemuMonitorTest *test = opaque;
    bool err = false;
    VIR_LOCK_GUARD lock = virLockGuardLock(&test->lock);

    if (test->quit)
        return;

    if (events & VIR_EVENT_HANDLE_WRITABLE) {
        ssize_t ret;
        if ((ret = virNetSocketWrite(sock,
                                     test->outgoing,
                                     test->outgoingLength)) < 0) {
            err = true;
            goto cleanup;
        }

        memmove(test->outgoing,
                test->outgoing + ret,
                test->outgoingLength - ret);
        test->outgoingLength -= ret;

        if ((test->outgoingCapacity - test->outgoingLength) > 1024)
            VIR_SHRINK_N(test->outgoing, test->outgoingCapacity, 1024);
    }

    if (events & VIR_EVENT_HANDLE_READABLE) {
        ssize_t ret, used;
        char *t1, *t2;

        if ((test->incomingCapacity - test->incomingLength) < 1024) {
            VIR_EXPAND_N(test->incoming, test->incomingCapacity, 1024);
        }

        if ((ret = virNetSocketRead(sock,
                                    test->incoming + test->incomingLength,
                                    (test->incomingCapacity - test->incomingLength) - 1)) < 0) {
            err = true;
            goto cleanup;
        }
        test->incomingLength += ret;
        test->incoming[test->incomingLength] = '\0';

        /* Look to see if we've got a complete line, and
         * if so, handle that command
         */
        t1 = test->incoming;
        while ((t2 = strstr(t1, "\n")) ||
                (test->agent && (t2 = strstr(t1, "\r")))) {
            *t2 = '\0';

            if (qemuMonitorTestProcessCommand(test, t1) < 0) {
                err = true;
                goto cleanup;
            }

            t1 = t2 + 1;
        }
        used = t1 - test->incoming;
        memmove(test->incoming, t1, test->incomingLength - used);
        test->incomingLength -= used;
        if ((test->incomingCapacity - test->incomingLength) > 1024) {
            VIR_SHRINK_N(test->incoming,
                         test->incomingCapacity,
                         1024);
        }
    }

    if (events & (VIR_EVENT_HANDLE_HANGUP |
                  VIR_EVENT_HANDLE_ERROR))
        err = true;

 cleanup:
    if (err) {
        virNetSocketRemoveIOCallback(sock);
        virNetSocketClose(sock);
        g_clear_pointer(&test->client, virObjectUnref);
    } else {
        events = VIR_EVENT_HANDLE_READABLE;

        if (test->outgoingLength)
            events |= VIR_EVENT_HANDLE_WRITABLE;

        virNetSocketUpdateIOCallback(sock, events);
    }
}


static void
qemuMonitorTestWorker(void *opaque)
{
    qemuMonitorTest *test = opaque;

    while (true) {
        VIR_WITH_MUTEX_LOCK_GUARD(&test->lock) {
            if (test->quit) {
                test->running = false;
                return;
            }
        }

        if (virEventRunDefaultImpl() < 0) {
            VIR_WITH_MUTEX_LOCK_GUARD(&test->lock) {
                test->quit = true;
                test->running = false;
                return;
            }
        }
    }
}


static void
qemuMonitorTestFreeTimer(int timer G_GNUC_UNUSED,
                         void *opaque G_GNUC_UNUSED)
{
    /* nothing to be done here */
}


void
qemuMonitorTestFree(qemuMonitorTest *test)
{
    size_t i;
    int timer = -1;

    if (!test)
        return;

    VIR_WITH_MUTEX_LOCK_GUARD(&test->lock) {
        if (test->running) {
            test->quit = true;
            /* HACK: Add a dummy timeout to break event loop */
            timer = virEventAddTimeout(0, qemuMonitorTestFreeTimer, NULL, NULL);
        }
    }

    if (test->client) {
        virNetSocketRemoveIOCallback(test->client);
        virNetSocketClose(test->client);
        virObjectUnref(test->client);
    }

    virObjectUnref(test->server);
    if (test->mon) {
        virObjectUnlock(test->mon);
        qemuMonitorClose(test->mon);
    }

    if (test->agent) {
        virObjectUnlock(test->agent);
        qemuAgentClose(test->agent);
    }

    g_object_unref(test->eventThread);

    virObjectUnref(test->vm);

    if (test->started)
        virThreadJoin(&test->thread);

    if (timer != -1)
        virEventRemoveTimeout(timer);

    g_free(test->incoming);
    g_free(test->outgoing);

    for (i = 0; i < test->nitems; i++) {
        if (!test->allowUnusedCommands) {
            g_fprintf(stderr,
                      "\nunused test monitor item '%s'",
                      NULLSTR(test->items[i]->identifier));
        }

        qemuMonitorTestItemFree(test->items[i]);
    }
    g_free(test->items);

    if (test->tmpdir && rmdir(test->tmpdir) < 0)
        VIR_WARN("Failed to remove tempdir: %s", g_strerror(errno));

    g_free(test->tmpdir);

    if (!test->allowUnusedCommands &&
        test->nitems != 0) {
        qemuMonitorTestError("unused test monitor items are not allowed for this test\n");
    }

    virMutexDestroy(&test->lock);
    g_free(test);
}


void
qemuMonitorTestAddHandler(qemuMonitorTest *test,
                          const char *identifier,
                          qemuMonitorTestResponseCallback cb,
                          void *opaque,
                          virFreeCallback freecb)
{
    qemuMonitorTestItem *item;

    item = g_new0(qemuMonitorTestItem, 1);

    item->identifier = g_strdup(identifier);
    item->cb = cb;
    item->freecb = freecb;
    item->opaque = opaque;

    VIR_WITH_MUTEX_LOCK_GUARD(&test->lock) {
        VIR_APPEND_ELEMENT(test->items, test->nitems, item);
    }
}

void *
qemuMonitorTestItemGetPrivateData(qemuMonitorTestItem *item)
{
    return item ? item->opaque : NULL;
}


struct qemuMonitorTestHandlerData {
    char *command_name;
    char *cmderr;
    char *response;
    char *expectArgs;
};

static void
qemuMonitorTestHandlerDataFree(void *opaque)
{
    struct qemuMonitorTestHandlerData *data = opaque;

    if (!data)
        return;

    g_free(data->command_name);
    g_free(data->cmderr);
    g_free(data->response);
    g_free(data->expectArgs);
    g_free(data);
}


/* Returns -1 on error, 0 if validation was successful/not necessary */
static int
qemuMonitorTestProcessCommandDefaultValidate(qemuMonitorTest *test,
                                             const char *cmdname,
                                             virJSONValue *args)
{
    g_auto(virBuffer) debug = VIR_BUFFER_INITIALIZER;
    bool allowIncomplete = false;

    if (!test->qapischema)
        return 0;

    if (test->agent) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       "Command validation testing is not "
                       "implemented for the guest agent");
        return -1;
    }

    /* The schema of 'device_add' is incomplete so we relax the validator */
    if (STREQ(cmdname, "device_add"))
        allowIncomplete = true;

    if (testQEMUSchemaValidateCommand(cmdname, args, test->qapischema,
                                      test->skipValidationDeprecated,
                                      test->skipValidationRemoved,
                                      allowIncomplete,
                                      &debug) < 0) {
        if (virTestGetDebug() == 2) {
            g_autofree char *argstr = NULL;

            if (args)
                argstr = virJSONValueToString(args, true);

            fprintf(stderr,
                    "\nfailed to validate arguments of '%s' against QAPI schema\n"
                    "args:\n%s\nvalidator output:\n %s\n",
                    cmdname, NULLSTR(argstr), virBufferCurrentContent(&debug));
        }

        qemuMonitorTestError("failed to validate arguments of '%s' "
                             "against QAPI schema "
                             "(to see debug output use VIR_TEST_DEBUG=2)",
                             cmdname);
        return -1;
    }

    return 0;
}


static int
qemuMonitorTestProcessCommandDefault(qemuMonitorTest *test,
                                     qemuMonitorTestItem *item,
                                     const char *cmdstr)
{
    struct qemuMonitorTestHandlerData *data = item->opaque;
    g_autoptr(virJSONValue) val = NULL;
    virJSONValue *cmdargs = NULL;
    const char *cmdname;

    if (!(val = virJSONValueFromString(cmdstr)))
        return -1;

    if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
        qemuMonitorTestError("Missing command name in %s", cmdstr);
        return -1;
    }

    cmdargs = virJSONValueObjectGet(val, "arguments");
    if (qemuMonitorTestProcessCommandDefaultValidate(test, cmdname, cmdargs) < 0)
        return -1;

    if (data->command_name && STRNEQ(data->command_name, cmdname)) {
        qemuMonitorTestErrorInvalidCommand(data->command_name, cmdname);
        return -1;
    } else {
        return qemuMonitorTestAddResponse(test, data->response);
    }
}


int
qemuMonitorTestAddItem(qemuMonitorTest *test,
                       const char *command_name,
                       const char *response)
{
    struct qemuMonitorTestHandlerData *data;

    data = g_new0(struct qemuMonitorTestHandlerData, 1);

    data->command_name = g_strdup(command_name);
    data->response = g_strdup(response);

    qemuMonitorTestAddHandler(test,
                              command_name,
                              qemuMonitorTestProcessCommandDefault,
                              data, qemuMonitorTestHandlerDataFree);

    return 0;
}


static int
qemuMonitorTestProcessCommandVerbatim(qemuMonitorTest *test,
                                      qemuMonitorTestItem *item,
                                      const char *cmdstr)
{
    struct qemuMonitorTestHandlerData *data = item->opaque;
    g_autofree char *reformatted = NULL;
    g_autoptr(virJSONValue) json = NULL;
    virJSONValue *cmdargs;
    const char *cmdname;
    int ret = -1;

    /* JSON strings will be reformatted to simplify checking */
    if (!(json = virJSONValueFromString(cmdstr)) ||
        !(reformatted = virJSONValueToString(json, false)))
        return -1;

    cmdstr = reformatted;

    /* in this case we do a best-effort schema check if we can find the command */
    if ((cmdname = virJSONValueObjectGetString(json, "execute"))) {
        cmdargs = virJSONValueObjectGet(json, "arguments");
        if (qemuMonitorTestProcessCommandDefaultValidate(test, cmdname, cmdargs) < 0)
            return -1;
    }

    if (STREQ(data->command_name, cmdstr)) {
        ret = qemuMonitorTestAddResponse(test, data->response);
    } else {
        if (data->cmderr) {
            qemuMonitorTestError("%s: %s expected %s",
                                 data->cmderr, cmdstr, data->command_name);
        } else {
            qemuMonitorTestErrorInvalidCommand(data->command_name, cmdstr);
        }
    }

    return ret;
}


/**
 * qemuMonitorTestAddItemVerbatim:
 * @test: monitor test object
 * @command: full expected command syntax
 * @cmderr: possible explanation of expected command (may be NULL)
 * @response: full reply of @command
 *
 * Adds a test command for the simulated monitor. The full syntax is checked
 * as specified in @command. For JSON monitor tests formatting/whitespace is
 * ignored. If the command on the monitor is not as expected an error containing
 * @cmderr is returned. Otherwise @response is put as-is on the monitor.
 *
 * Returns 0 when command was successfully added, -1 on error.
 */
int
qemuMonitorTestAddItemVerbatim(qemuMonitorTest *test,
                               const char *command,
                               const char *cmderr,
                               const char *response)
{
    struct qemuMonitorTestHandlerData *data;
    char *reformatted = NULL;

    if (!(reformatted = virJSONStringReformat(command, false)))
        return -1;

    data = g_new0(struct qemuMonitorTestHandlerData, 1);

    data->response = g_strdup(response);
    data->cmderr = g_strdup(cmderr);
    data->command_name = g_steal_pointer(&reformatted);

    qemuMonitorTestAddHandler(test,
                              command,
                              qemuMonitorTestProcessCommandVerbatim,
                              data, qemuMonitorTestHandlerDataFree);

    return 0;
}


static int
qemuMonitorTestProcessGuestAgentSync(qemuMonitorTest *test,
                                     qemuMonitorTestItem *item G_GNUC_UNUSED,
                                     const char *cmdstr)
{
    g_autoptr(virJSONValue) val = NULL;
    virJSONValue *args;
    unsigned long long id;
    const char *cmdname;
    g_autofree char *retmsg = NULL;

    if (!(val = virJSONValueFromString(cmdstr)))
        return -1;

    if (!(cmdname = virJSONValueObjectGetString(val, "execute")))
        return qemuMonitorTestAddErrorResponse(test, "Missing guest-sync command name");

    if (STRNEQ(cmdname, "guest-sync"))
        return qemuMonitorTestAddInvalidCommandResponse(test, "guest-sync", cmdname);

    if (!(args = virJSONValueObjectGet(val, "arguments")))
        return qemuMonitorTestAddErrorResponse(test, "Missing arguments for guest-sync");

    if (virJSONValueObjectGetNumberUlong(args, "id", &id))
        return qemuMonitorTestAddErrorResponse(test, "Missing id for guest sync");

    retmsg = g_strdup_printf("{\"return\":%llu}", id);

    return qemuMonitorTestAddResponse(test, retmsg);
}


int
qemuMonitorTestAddAgentSyncResponse(qemuMonitorTest *test)
{
    if (!test->agent) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       "This test is not an agent test");
        return -1;
    }

    qemuMonitorTestAddHandler(test,
                              "agent-sync",
                              qemuMonitorTestProcessGuestAgentSync,
                              NULL, NULL);

    return 0;
}


static void
qemuMonitorTestEOFNotify(qemuMonitor *mon G_GNUC_UNUSED,
                         virDomainObj *vm G_GNUC_UNUSED)
{
}


static void
qemuMonitorTestErrorNotify(qemuMonitor *mon G_GNUC_UNUSED,
                           virDomainObj *vm G_GNUC_UNUSED)
{
}


static qemuMonitorCallbacks qemuMonitorTestCallbacks = {
    .eofNotify = qemuMonitorTestEOFNotify,
    .errorNotify = qemuMonitorTestErrorNotify,
    .domainDeviceDeleted = qemuProcessHandleDeviceDeleted,
};


static void
qemuMonitorTestAgentNotify(qemuAgent *agent G_GNUC_UNUSED,
                           virDomainObj *vm G_GNUC_UNUSED)
{
}


static qemuAgentCallbacks qemuMonitorTestAgentCallbacks = {
    .eofNotify = qemuMonitorTestAgentNotify,
    .errorNotify = qemuMonitorTestAgentNotify,
};


static qemuMonitorTest *
qemuMonitorCommonTestNew(virDomainXMLOption *xmlopt,
                         virDomainObj *vm,
                         virDomainChrSourceDef *src)
{
    g_autoptr(qemuMonitorTest) test = NULL;
    g_autofree char *path = NULL;
    g_autofree char *tmpdir_template = NULL;

    test = g_new0(qemuMonitorTest, 1);

    if (virMutexInit(&test->lock) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       "Cannot initialize mutex");
        VIR_FREE(test);
        return NULL;
    }

    tmpdir_template = g_strdup("/tmp/libvirt_XXXXXX");

    if (!(test->tmpdir = g_mkdtemp(tmpdir_template))) {
        virReportSystemError(errno, "%s",
                             "Failed to create temporary directory");
        return NULL;
    }

    tmpdir_template = NULL;

    path = g_strdup_printf("%s/qemumonitorjsontest.sock", test->tmpdir);

    if (vm) {
        test->vm = virObjectRef(vm);
    } else {
        test->vm = virDomainObjNew(xmlopt);
        if (!test->vm)
            return NULL;
        if (!(test->vm->def = virDomainDefNew(xmlopt)))
            return NULL;
    }

    if (virNetSocketNewListenUNIX(path, 0700, geteuid(), getegid(),
                                  &test->server) < 0)
        return NULL;

    memset(src, 0, sizeof(*src));
    src->type = VIR_DOMAIN_CHR_TYPE_UNIX;
    src->data.nix.path = (char *)path;
    src->data.nix.listen = false;
    path = NULL;

    if (virNetSocketListen(test->server, 1) < 0)
        return NULL;

    return g_steal_pointer(&test);
}


static int
qemuMonitorCommonTestInit(qemuMonitorTest *test)
{
    int events = VIR_EVENT_HANDLE_READABLE;

    if (!test)
        return -1;

    if (virNetSocketAccept(test->server, &test->client) < 0)
        return -1;

    if (!test->client)
        return -1;

    if (test->outgoingLength > 0)
        events = VIR_EVENT_HANDLE_WRITABLE;

    if (virNetSocketAddIOCallback(test->client,
                                  events,
                                  qemuMonitorTestIO,
                                  test,
                                  NULL) < 0)
        return -1;

    VIR_WITH_MUTEX_LOCK_GUARD(&test->lock) {
        if (virThreadCreate(&test->thread, true, qemuMonitorTestWorker, test) < 0)
            return -1;
        test->started = test->running = true;
    }

    return 0;
}


#define QEMU_JSON_GREETING  "{\"QMP\":"\
                            "   {\"version\":"\
                            "       {\"qemu\":"\
                            "           {\"micro\": 1,"\
                            "            \"minor\": 0,"\
                            "            \"major\": 1"\
                            "           },"\
                            "        \"package\": \"(qemu-kvm-1.0.1)"\
                            "       \"},"\
                            "    \"capabilities\": []"\
                            "   }"\
                            "}"
/* We skip the normal handshake reply of "{\"execute\":\"qmp_capabilities\"}" */


qemuMonitorTest *
qemuMonitorTestNew(virDomainXMLOption *xmlopt,
                   virDomainObj *vm,
                   const char *greeting,
                   GHashTable *schema)
{
    g_autoptr(qemuMonitorTest) test = NULL;
    virDomainChrSourceDef src = { 0 };

    if (!(test = qemuMonitorCommonTestNew(xmlopt, vm, &src)))
        goto error;

    if (!(test->eventThread = virEventThreadNew("mon-test")))
        goto error;

    test->qapischema = schema;
    if (!(test->mon = qemuMonitorOpen(test->vm,
                                      &src,
                                      virEventThreadGetContext(test->eventThread),
                                      &qemuMonitorTestCallbacks)))
        goto error;

    virObjectLock(test->mon);

    if (!greeting)
        greeting = QEMU_JSON_GREETING;

    if (qemuMonitorTestAddResponse(test, greeting) < 0)
        goto error;

    if (qemuMonitorCommonTestInit(test) < 0)
        goto error;

    virDomainChrSourceDefClear(&src);

    return g_steal_pointer(&test);

 error:
    virDomainChrSourceDefClear(&src);
    return NULL;
}


/**
 * qemuMonitorTestNewFromFile:
 * @fileName: File name to load monitor replies from
 * @xmlopt: XML parser configuration object
 * @simple: see below
 *
 * Create a JSON test monitor simulator object and fill it with replies
 * specified in @fileName. The file contains JSON reply objects separated by
 * empty lines. If @simple is true a generic QMP greeting is automatically
 * added as the first reply, otherwise the first entry in the file is used.
 *
 * Returns the monitor object on success; NULL on error.
 */
qemuMonitorTest *
qemuMonitorTestNewFromFile(const char *fileName,
                           virDomainXMLOption *xmlopt,
                           bool simple)
{
    g_autoptr(qemuMonitorTest) test = NULL;
    g_autofree char *json = NULL;
    char *tmp;
    char *singleReply;

    if (virTestLoadFile(fileName, &json) < 0)
        return NULL;

    if (simple && !(test = qemuMonitorTestNewSimple(xmlopt)))
        return NULL;

    /* Our JSON parser expects replies to be separated by a newline character.
     * Hence we must preprocess the file a bit. */
    tmp = singleReply = json;
    while ((tmp = strchr(tmp, '\n'))) {
        /* It is safe to touch tmp[1] since all strings ends with '\0'. */
        bool eof = !tmp[1];

        if (*(tmp + 1) != '\n') {
            *tmp = ' ';
            tmp++;
        } else {
            /* Cut off a single reply. */
            *(tmp + 1) = '\0';

            if (test) {
                if (qemuMonitorTestAddItem(test, NULL, singleReply) < 0)
                    return NULL;
            } else {
                /* Create new mocked monitor with our greeting */
                if (!(test = qemuMonitorTestNew(xmlopt, NULL,
                                                singleReply, NULL)))
                    return NULL;
            }

            if (!eof) {
                /* Move the @tmp and @singleReply. */
                tmp += 2;
                singleReply = tmp;
            }
        }

        if (eof)
            break;
    }

    if (test && qemuMonitorTestAddItem(test, NULL, singleReply) < 0)
        return NULL;

    return g_steal_pointer(&test);
}


/**
 * qemuMonitorTestAllowUnusedCommands:
 * @test: test monitor object
 *
 * By default all test items/commands must be used by the test. This function
 * allows to override the requirement for individual tests e.g. if it's necessary
 * to test some negative scenarios which would not use all commands.
 */
void
qemuMonitorTestAllowUnusedCommands(qemuMonitorTest *test)
{
    test->allowUnusedCommands = true;
}


/**
 * qemuMonitorTestSkipDeprecatedValidation:
 * @test: test monitor object
 * @allowRemoved: don't produce errors if command was removed from QMP schema
 *
 * By default if the QMP schema is provided all test items/commands are
 * validated against the schema. This function allows to override the validation
 * and additionally if @allowRemoved is true and if such a command is no longer
 * present in the QMP, only a warning is printed.
 *
 * '@allowRemoved' must be used only if a suitable replacement is already in
 * use and the code tests legacy interactions.
 *
 * Note that currently '@allowRemoved' influences only removed commands. If an
 * argument is removed it will still fail validation.
 */
void
qemuMonitorTestSkipDeprecatedValidation(qemuMonitorTest *test,
                                        bool allowRemoved)
{
    test->skipValidationDeprecated = true;
    test->skipValidationRemoved = allowRemoved;
}


static int
qemuMonitorTestFullAddItem(qemuMonitorTest *test,
                           const char *filename,
                           const char *command,
                           const char *response,
                           size_t line)
{
    g_autofree char *cmderr = NULL;

    cmderr = g_strdup_printf("wrong expected command in %s:%zu: ", filename, line);

    return qemuMonitorTestAddItemVerbatim(test, command, cmderr, response);
}


struct qemuMonitorTestCommandReplyTuple {
    const char *command;
    const char *reply;
    size_t line; /* line number of @command */
};


/**
 * qemuMonitorTestProcessFileEntries:
 * @inputstr: input file contents (modified)
 * @fileName: File name of @inputstr (for error reporting)
 * @items: filled with command, reply tuples
 * @nitems: Count of elements in @items.
 *
 * Process a monitor interaction file.
 *
 * The file contains a sequence of JSON commands and reply objects separated by
 * empty lines. A command is followed by a reply.
 */
static int
qemuMonitorTestProcessFileEntries(char *inputstr,
                                  const char *fileName,
                                  struct qemuMonitorTestCommandReplyTuple **items,
                                  size_t *nitems)
{
    size_t nalloc = 0;
    char *tmp = inputstr;
    size_t line = 0;
    char *command = inputstr;
    char *response = NULL;
    size_t commandln = 0;

    *items = NULL;
    *nitems = 0;

    while ((tmp = strchr(tmp, '\n'))) {
        line++;

        /* eof */
        if (!tmp[1])
            break;

        /* concatenate block which was broken up for readability */
        if (*(tmp + 1) != '\n') {
            *tmp = ' ';
            tmp++;
            continue;
        }

        /* We've seen a new line, increment the counter */
        line++;

        /* Cut off a single reply. */
        *(tmp + 1) = '\0';

        if (response) {
            struct qemuMonitorTestCommandReplyTuple *item;

            VIR_RESIZE_N(*items, nalloc, *nitems, 1);

            item = *items + *nitems;

            item->command = g_steal_pointer(&command);
            item->reply = g_steal_pointer(&response);
            item->line = commandln;
            (*nitems)++;
        }

        /* Move the @tmp and @singleReply. */
        tmp += 2;

        if (!command) {
            commandln = line;
            command = tmp;
        } else {
            response = tmp;
        }
    }

    if (command) {
        struct qemuMonitorTestCommandReplyTuple *item;

        if (!response) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "missing response for command "
                           "on line '%zu' in '%s'", commandln, fileName);
            return -1;
        }

        VIR_RESIZE_N(*items, nalloc, *nitems, 1);

        item = *items + *nitems;

        item->command = g_steal_pointer(&command);
        item->reply = g_steal_pointer(&response);
        item->line = commandln;
        (*nitems)++;
    }

    return 0;
}

/**
 * qemuMonitorTestNewFromFileFull:
 * @fileName: File name to load monitor replies from
 * @driver: qemu driver object
 * @vm: domain object (may be null if it's not needed by the test)
 * @qmpschema: QMP schema data hash table if QMP checking is required
 *
 * Create a JSON test monitor simulator object and fill it with expected command
 * sequence and replies specified in @fileName.
 *
 * The file contains a sequence of JSON commands and reply objects separated by
 * empty lines. A command is followed by a reply. The QMP greeting is added
 * automatically.
 *
 * Returns the monitor object on success; NULL on error.
 */
qemuMonitorTest *
qemuMonitorTestNewFromFileFull(const char *fileName,
                               virQEMUDriver *driver,
                               virDomainObj *vm,
                               GHashTable *qmpschema)
{
    g_autoptr(qemuMonitorTest) ret = NULL;
    g_autofree char *jsonstr = NULL;
    g_autofree struct qemuMonitorTestCommandReplyTuple *items = NULL;
    size_t nitems = 0;
    size_t i;

    if (virTestLoadFile(fileName, &jsonstr) < 0)
        return NULL;

    if (!(ret = qemuMonitorTestNew(driver->xmlopt, vm, NULL, qmpschema)))
        return NULL;

    if (qemuMonitorTestProcessFileEntries(jsonstr, fileName, &items, &nitems) < 0)
        return NULL;

    for (i = 0; i < nitems; i++) {
        struct qemuMonitorTestCommandReplyTuple *item = items + i;

        if (qemuMonitorTestFullAddItem(ret, fileName, item->command, item->reply,
                                       item->line) < 0)
            return NULL;
    }

    return g_steal_pointer(&ret);
}


qemuMonitorTest *
qemuMonitorTestNewAgent(virDomainXMLOption *xmlopt)
{
    g_autoptr(qemuMonitorTest) test = NULL;
    virDomainChrSourceDef src = { 0 };

    if (!(test = qemuMonitorCommonTestNew(xmlopt, NULL, &src)))
        goto error;

    if (!(test->eventThread = virEventThreadNew("agent-test")))
        goto error;

    if (!(test->agent = qemuAgentOpen(test->vm,
                                      &src,
                                      virEventThreadGetContext(test->eventThread),
                                      &qemuMonitorTestAgentCallbacks)))
        goto error;

    virObjectLock(test->agent);

    if (qemuMonitorCommonTestInit(test) < 0)
        goto error;

    virDomainChrSourceDefClear(&src);

    return g_steal_pointer(&test);

 error:
    virDomainChrSourceDefClear(&src);
    return NULL;
}


qemuMonitor *
qemuMonitorTestGetMonitor(qemuMonitorTest *test)
{
    return test->mon;
}


qemuAgent *
qemuMonitorTestGetAgent(qemuMonitorTest *test)
{
    return test->agent;
}


virDomainObj *
qemuMonitorTestGetDomainObj(qemuMonitorTest *test)
{
    return test->vm;
}