There are some units within libvirt that utilize virCommand API to run
some commands and deserve own unit testing. These units are, however,
not desired to be rewritten to dig virCommand API usage out. As a great
example virNetDevBandwidth could be used. The problem with the bandwidth
unit is: it uses virComamnd API heavily. Therefore we need a mechanism
to not really run a command, but rather see its string representation
after which we can decide if the unit construct the correct sequence of
commands or not.
Signed-off-by: Michal Privoznik <mprivozn(a)redhat.com>
---
src/libvirt_private.syms | 1 +
src/util/vircommand.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++-
src/util/vircommand.h | 2 ++
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index f9bdd8c..814c74c 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1096,6 +1096,7 @@ virCommandRequireHandshake;
virCommandRun;
virCommandRunAsync;
virCommandSetAppArmorProfile;
+virCommandSetDryRun;
virCommandSetErrorBuffer;
virCommandSetErrorFD;
virCommandSetGID;
diff --git a/src/util/vircommand.c b/src/util/vircommand.c
index a52a1ab..11e827f 100644
--- a/src/util/vircommand.c
+++ b/src/util/vircommand.c
@@ -129,6 +129,9 @@ struct _virCommand {
#endif
};
+/* See virCommandSetDryRun for description on this variable */
+static const char *outfile;
+
/*
* virCommandFDIsSet:
* @fd: FD to test
@@ -2263,7 +2266,21 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid)
str = virCommandToString(cmd);
VIR_DEBUG("About to run %s", str ? str : cmd->args[0]);
- VIR_FREE(str);
+ if (outfile) {
+ /* Dry-run is requested. Just append @str to @outfile
+ * and claim success. */
+
+ VIR_DEBUG("Dry run requested, writing stringified command to %s",
outfile);
+ if (virFileAppendStr(outfile, str ? str : cmd->args[0],
+ S_IRUSR | S_IWUSR, true) < 0) {
+ virReportSystemError(errno, _("unable to append to %s"),
+ outfile);
+ goto cleanup;
+ }
+
+ ret = 0;
+ goto cleanup;
+ }
ret = virExec(cmd);
VIR_DEBUG("Command result %d, with PID %d",
@@ -2303,6 +2320,7 @@ cleanup:
VIR_FORCE_CLOSE(cmd->infd);
VIR_FORCE_CLOSE(cmd->inpipe);
}
+ VIR_FREE(str);
return ret;
}
@@ -2334,6 +2352,14 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
return -1;
}
+ if (outfile) {
+ /* Dry-run requested. Claim success. */
+ VIR_DEBUG("Dry run requested, claiming success");
+ if (exitstatus)
+ *exitstatus = 0;
+ return 0;
+ }
+
if (cmd->pid == -1) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("command is not yet running"));
@@ -2669,3 +2695,33 @@ virCommandDoAsyncIO(virCommandPtr cmd)
cmd->flags |= VIR_EXEC_ASYNC_IO | VIR_EXEC_NONBLOCK;
}
+
+/**
+ * virCommandSetDryRun:
+ * @file: destination file
+ *
+ * Sometimes it's desired to not actually run given command, but
+ * see its string representation without having to change the
+ * callee. Unit testing serves as a great example. In such cases,
+ * the callee constructs the command and calls it via
+ * virCommandRun* API. The virCommandSetDryRun allows you to
+ * modify this behavior: once called, every call to
+ * virCommandRun* results in command string representation being
+ * appended to @file instead of being executed. For example:
+ *
+ * virCommandSetDryRun("/tmp/test.txt");
+ *
+ * const char *const echocmd[] = {"/bin/echo", "Hello world"}
+ * virCommandRun(echocmd, NULL);
+ *
+ * After this, there's /tmp/test.txt with contents of:
+ *
+ * /bin/echo Hello world
+ *
+ * To cancel this effect pass NULL.
+ */
+void
+virCommandSetDryRun(const char *file)
+{
+ outfile = file;
+}
diff --git a/src/util/vircommand.h b/src/util/vircommand.h
index e977f93..d942c5b 100644
--- a/src/util/vircommand.h
+++ b/src/util/vircommand.h
@@ -184,4 +184,6 @@ void virCommandAbort(virCommandPtr cmd);
void virCommandFree(virCommandPtr cmd);
void virCommandDoAsyncIO(virCommandPtr cmd);
+
+void virCommandSetDryRun(const char *file);
#endif /* __VIR_COMMAND_H__ */
--
1.8.5.2