#include <config.h> #include <unistd.h> #include <fcntl.h> #include "internal.h" #include "testutils.h" #include "vircommand.h" #include "util/virthread.h" #define VIR_FROM_THIS VIR_FROM_NONE #ifdef WIN32 int main(void) { return EXIT_AM_SKIP; } #else static void testFilterLine(char *buffer, const char *toRemove) { char *start; while ((start = strstr(buffer, toRemove))) { char *end; if (!(end = strstr(start+1, "\n"))) { *start = '\0'; } else { memmove(start, end, strlen(end)+1); } } } static int testCompareOutputLit(const char *expectFile, const char *filter, const char *const *env, const char *const argv[]) { g_autofree char *actual = NULL; const char *empty = ""; g_autoptr(virCommand) cmd = NULL; int exitstatus = 0; cmd = virCommandNewArgs(argv); virCommandAddEnvString(cmd, "LANG=C"); while (env && *env) { virCommandAddEnvString(cmd, *env); env++; } virCommandSetInputBuffer(cmd, empty); virCommandSetOutputBuffer(cmd, &actual); virCommandSetErrorBuffer(cmd, &actual); if (virCommandRun(cmd, &exitstatus) < 0) return -1; if (exitstatus != 0) { g_autofree char *tmp = g_steal_pointer(&actual); actual = g_strdup_printf("%s\n## Exit code: %d\n", tmp, exitstatus); } if (filter) testFilterLine(actual, filter); if (virTestCompareToFileFull(actual, expectFile, false) < 0) return -1; return 0; } # define VIRSH_DEFAULT abs_top_builddir "/tools/virsh", \ "--connect", \ "test:///default" static char *custom_uri; # define VIRSH_CUSTOM abs_top_builddir "/tools/virsh", \ "--connect", \ custom_uri struct testInfo { const char *testname; /* used to generate output filename */ const char *filter; const char *const *argv; bool expensive; const char *const *env; /* extra environment variables to pass */ bool forbid_root; bool need_readline; }; static int testCompare(const void *data) { const struct testInfo *info = data; g_autofree char *outfile = NULL; if (info->expensive && virTestGetExpensive() == 0) return EXIT_AM_SKIP; if (info->forbid_root && geteuid() == 0) return EXIT_AM_SKIP; # ifndef WITH_READLINE if (info->need_readline) return EXIT_AM_SKIP; # endif if (info->testname) { outfile = g_strdup_printf("%s/virshtestdata/%s.out", abs_srcdir, info->testname); } return testCompareOutputLit(outfile, info->filter, info->env, info->argv); } static void testPipeFeeder(void *opaque) { /* feed more than observed buffer size which was historically 128k in the * test this was adapted from */ size_t emptyspace = 140 * 1024; const char *pipepath = opaque; const char *xml = "<domain type='test' id='2'>\n" " <name>t2</name>\n" " <uuid>004b96e1-2d78-c30f-5aa5-000000000000</uuid>\n" " <memory>8388608</memory>\n" " <vcpu>2</vcpu>\n" " <os>\n" " <type>xen</type>\n" " </os>\n" "</domain>\n"; size_t xmlsize = strlen(xml); g_autofree char *doc = g_new0(char, emptyspace + xmlsize + 1); VIR_AUTOCLOSE fd = -1; if ((fd = open(pipepath, O_WRONLY)) < 0) { fprintf(stderr, "\nfailed to open pipe '%s': %s\n", pipepath, g_strerror(errno)); return; } memset(doc, ' ', emptyspace); g_assert(virStrcpy(doc + emptyspace, xml, xmlsize + 1) == 0); if (safewrite(fd, doc, emptyspace + xmlsize) < 0) { fprintf(stderr, "\nfailed to write to pipe '%s': %s\n", pipepath, g_strerror(errno)); return; } } static int testVirshPipe(const void *data G_GNUC_UNUSED) { char tmpdir[] = "/tmp/libvirt_virshtest_XXXXXXX"; g_autofree char *pipepath = NULL; virThread feeder; bool join = false; g_autofree char *cmdstr = NULL; const char *argv[] = { VIRSH_DEFAULT, NULL, NULL }; int ret = -1; if (!g_mkdtemp(tmpdir)) { fprintf(stderr, "\nfailed to create temporary directory\n"); return -1; } pipepath = g_strdup_printf("%s/pipe", tmpdir); cmdstr = g_strdup_printf("define %s ; list --all", pipepath); argv[3] = cmdstr; if (mkfifo(pipepath, 0600) < 0) { fprintf(stderr, "\nfailed to create pipe '%s': %s\n", pipepath, g_strerror(errno)); goto cleanup; } if (virThreadCreate(&feeder, true, testPipeFeeder, pipepath) < 0) goto cleanup; join = true; if (testCompareOutputLit(abs_srcdir "/virshtestdata/read-big-pipe.out", "/tmp/libvirt_virshtest", NULL, argv) < 0) goto cleanup; ret = 0; cleanup: if (join) virThreadJoin(&feeder); unlink(pipepath); rmdir(tmpdir); return ret; } static int mymain(void) { int ret = 0; bool need_readline = false; custom_uri = g_strdup_printf("test://%s/../examples/xml/test/testnode.xml", abs_srcdir); # define DO_TEST_SCRIPT_FULL(testname_, expensive, testfilter, ...) \ { \ const char *testname = testname_; \ g_autofree char *infile = g_strdup_printf("%s/virshtestdata/%s.in", \ abs_srcdir, testname); \ const char *myargv[] = { __VA_ARGS__, NULL, NULL }; \ const char **tmp = myargv; \ const struct testInfo info = { testname, testfilter, myargv, expensive, NULL, false, need_readline}; \ g_autofree char *scriptarg = NULL; \ if (virFileReadAll(infile, 256 * 1024, &scriptarg) < 0) { \ fprintf(stderr, "\nfailed to load '%s'\n", infile); \ ret = -1; \ } \ while (*tmp) \ tmp++; \ *tmp = scriptarg; \ if (virTestRun(testname, testCompare, &info) < 0) \ ret = -1; \ } while (0); # define DO_TEST_SCRIPT(testname_, testfilter, ...) \ DO_TEST_SCRIPT_FULL(testname_, false, testfilter, __VA_ARGS__); DO_TEST_SCRIPT("info-default", NULL, VIRSH_DEFAULT); DO_TEST_SCRIPT("info-custom", NULL, VIRSH_CUSTOM); DO_TEST_SCRIPT("domain-id", "\nCPU time:", VIRSH_CUSTOM); DO_TEST_SCRIPT("blkiotune", NULL, VIRSH_CUSTOM); DO_TEST_SCRIPT("iothreads", NULL, VIRSH_CUSTOM); # define DO_TEST_INFO(infostruct) \ if (virTestRun((infostruct)->testname, testCompare, (infostruct)) < 0) \ ret = -1; # define DO_TEST_FULL(testname, filter, ...) \ do { \ const char *myargv[] = { __VA_ARGS__, NULL }; \ const struct testInfo info = { testname, NULL, myargv, false, NULL, false, need_readline }; \ DO_TEST_INFO(&info); \ } while (0) /* automatically numbered test invocation */ # define DO_TEST(...) \ DO_TEST_FULL(virTestCounterNext(), NULL, VIRSH_DEFAULT, __VA_ARGS__); /* Arg parsing quote removal tests. */ virTestCounterReset("echo-quote-removal-"); DO_TEST("echo a \t b"); DO_TEST("echo \"a \t b\""); DO_TEST("echo 'a \t b'"); DO_TEST("echo a\\ \\\t\\ b"); DO_TEST("echo", "'", "\"", "\\;echo\ta"); DO_TEST("echo \\' \\\" \\;echo\ta"); DO_TEST("echo \\' \\\" \\\\;echo\ta"); DO_TEST("echo \"'\" '\"' '\\'\"\\\\\""); /* Tests of echo flags. */ DO_TEST_SCRIPT("echo-escaping", NULL, VIRSH_DEFAULT); virTestCounterReset("echo-escaping-"); DO_TEST("echo", "a", "A", "0", "+", "*", ";", ".", "'", "\"", "/", "?", "=", " ", "\n", "<", ">", "&"); DO_TEST("echo", "--shell", "a", "A", "0", "+", "*", ";", ".", "'", "\"", "/", "?", "=", " ", "\n", "<", ">", "&"); DO_TEST("echo", "--xml", "a", "A", "0", "+", "*", ";", ".", "'", "\"", "/", "?", "=", " ", "\n", "<", ">", "&"); /* Tests of -- handling. */ virTestCounterReset("dash-dash-argument-"); DO_TEST("--", "echo", "--shell", "a"); DO_TEST("--", "echo", "a", "--shell"); DO_TEST("--", "echo", "--", "a", "--shell"); DO_TEST("echo", "--", "--", "--shell", "a"); DO_TEST("echo --s\\h'e'\"l\"l -- a"); DO_TEST("echo \t '-'\"-\" \t --shell \t a"); /* Tests of alias handling. */ DO_TEST_SCRIPT("echo-alias", NULL, VIRSH_DEFAULT); DO_TEST_FULL("echo-alias-argv", NULL, VIRSH_DEFAULT, "echo", "--str", "hello"); /* Tests of multiple commands. */ virTestCounterReset("multiple-commands-"); DO_TEST(" echo a; echo b;"); DO_TEST("\necho a\n echo b\n"); DO_TEST("ec\\\nho a\n echo \\\n b;"); DO_TEST("\"ec\\\nho\" a\n echo \"\\\n b\";"); DO_TEST("ec\\\nho a\n echo '\\\n b';"); DO_TEST("echo a # b"); DO_TEST("echo a #b\necho c"); DO_TEST("echo a # b\\\necho c"); DO_TEST("echo a '#' b"); DO_TEST("echo a \\# b"); DO_TEST("#unbalanced; 'quotes\"\necho a # b"); DO_TEST("\\# ignored;echo a\n'#also' ignored"); /* test of the --help option handling */ DO_TEST_SCRIPT("help-option", NULL, VIRSH_DEFAULT, "-q"); /* test of splitting in vshStringToArray */ DO_TEST_SCRIPT("echo-split", NULL, VIRSH_DEFAULT, "-q"); /* comprehensive coverage of argument assignment */ DO_TEST_SCRIPT("argument-assignment", NULL, VIRSH_DEFAULT, "-k0", "-d0"); DO_TEST_SCRIPT("snapshot-create-args", NULL, VIRSH_DEFAULT, "-q"); DO_TEST_SCRIPT("numeric-parsing", NULL, VIRSH_DEFAULT); /* The 'numeric-parsing-event' invokes virsh event with a 1 second timeout, * thus is marked expensive */ DO_TEST_SCRIPT_FULL("numeric-parsing-event", true, NULL, VIRSH_DEFAULT); DO_TEST_SCRIPT("attach-disk", NULL, VIRSH_DEFAULT); DO_TEST_SCRIPT("vcpupin", NULL, VIRSH_DEFAULT); DO_TEST_SCRIPT("lifecycle", "\nCPU time:", VIRSH_CUSTOM); DO_TEST_FULL("domain-id-overflow", NULL, VIRSH_CUSTOM, "-q", "domname", "4294967298"); DO_TEST_FULL("schedinfo-invalid-argument", NULL, VIRSH_DEFAULT, "schedinfo", "1", "--set", "j=k"); DO_TEST_FULL("pool-define-as", NULL, VIRSH_DEFAULT, "-q", "pool-define-as", "--print-xml", "P", "dir", "src-host", "/src/path", "/src/dev", "S", "/target-path"); DO_TEST_SCRIPT("snapshot", "<creationTime", VIRSH_DEFAULT); DO_TEST_FULL("snapshot-redefine", NULL, VIRSH_DEFAULT, "cd " abs_srcdir "/virshtestdata ;" "echo 'Redefine must be in topological order; this will fail' ;" "snapshot-create test --redefine snapshot-s2.xml --validate ;" "echo 'correct order' ;" "snapshot-create test --redefine snapshot-s3.xml --validate ;" "snapshot-create test --redefine snapshot-s2.xml --current --validate ;" "snapshot-info test --current"); DO_TEST_SCRIPT("checkpoint", "<creationTime", VIRSH_DEFAULT); DO_TEST_FULL("checkpoint-redefine", NULL, VIRSH_DEFAULT, "cd " abs_srcdir "/virshtestdata ;" "echo 'Redefine must be in topological order; this will fail' ;" "checkpoint-create test --redefine checkpoint-c2.xml ;" "echo 'correct order' ;" "checkpoint-create test --redefine checkpoint-c3.xml ;" "checkpoint-create test --redefine checkpoint-c2.xml ;" "checkpoint-info test c2"); /* completion doesn't work on non-readline builds */ need_readline = true; DO_TEST_FULL("completion-command", NULL, VIRSH_CUSTOM, "complete", "--", "ech"); DO_TEST_FULL("completion-command-complete", NULL, VIRSH_CUSTOM, "complete", "--", "echo"); DO_TEST_FULL("completion-args", NULL, VIRSH_CUSTOM, "complete", "--", "echo", ""); DO_TEST_FULL("completion-arg-partial", NULL, VIRSH_CUSTOM, "complete", "--", "echo", "--xm", "--s"); DO_TEST_FULL("completion-arg-full-bool", NULL, VIRSH_CUSTOM, "complete", "--", "echo", "--nonexistant-arg", "--xml"); DO_TEST_FULL("completion-arg-full-bool-next", NULL, VIRSH_CUSTOM, "complete", "--", "echo", "--nonexistant-arg", "--xml", ""); DO_TEST_FULL("completion-arg-full-string", NULL, VIRSH_CUSTOM, "complete", "--", "echo", "--nonexistant-arg", "--prefix"); DO_TEST_FULL("completion-arg-full-string-next", NULL, VIRSH_CUSTOM, "complete", "--", "echo", "--nonexistant-arg", "--prefix", ""); DO_TEST_FULL("completion-arg-full-argv", NULL, VIRSH_CUSTOM, "complete", "--", "domstats", "--domain"); DO_TEST_FULL("completion-arg-full-argv-next", NULL, VIRSH_DEFAULT, "complete", "--", "domstats", "--domain", ""); DO_TEST_FULL("completion-argv-multiple", NULL, VIRSH_CUSTOM, "complete", "--", "domstats", "--domain", "fc", "--domain", "fv"); DO_TEST_FULL("completion-argv-multiple-next", NULL, VIRSH_CUSTOM, "complete", "--", "domstats", "--domain", "fv", "--domain", "fc", ""); DO_TEST_FULL("completion-argv-multiple-positional", NULL, VIRSH_CUSTOM, "complete", "--", "domstats", "--domain", "fc", "fv"); DO_TEST_FULL("completion-argv-multiple-positional-next", NULL, VIRSH_CUSTOM, "complete", "--", "domstats", "--domain", "fc", "fv", ""); DO_TEST_FULL("completion-arg-positional-empty", NULL, VIRSH_CUSTOM, "complete", "--", "domstate", ""); DO_TEST_FULL("completion-arg-positional-partial", NULL, VIRSH_CUSTOM, "complete", "--", "domstate", "fv"); DO_TEST_FULL("completion-arg-positional-partial-next", NULL, VIRSH_CUSTOM, "complete", "--", "domstate", "fv", ""); DO_TEST_SCRIPT("completion", NULL, VIRSH_DEFAULT); need_readline = false; if (virTestRun("read-big-pipe", testVirshPipe, NULL) < 0) ret = -1; /* Test precedence of URI lookup in virsh: * * Precedence is the following (lowest priority first): * * 1) if run as root, 'uri_default' from /etc/libvirtd/libvirt.conf, * otherwise qemu:///session. There is no way to mock this file for * virsh/libvirt.so and the user may have set anything in there that * would spoil the test, so we don't test this * * 2) 'uri_default' from $XDG_CONFIG_HOME/libvirt/libvirt.conf * * 3) LIBVIRT_DEFAULT_URI * * 4) VIRSH_DEFAULT_CONNECT_URI * * 5) parameter -c (--connect) * * There are two pre-prepared directories in tests/virshtestdata/ serving * as mock XDG_CONFIG_HOME containing the test configs. */ { const char *uriTest = "uri; connect; uri"; const char *myargv_noconnect[] = { abs_top_builddir "/tools/virsh", uriTest, NULL }; const char *xdgDirBad = "XDG_CONFIG_HOME=" abs_srcdir "/virshtestdata/uriprecedence-xdg/bad/"; struct testInfo info = { NULL, NULL, myargv_noconnect, false, NULL, false, false }; /* test 1 - default from config */ { const char *myenv[] = { "XDG_CONFIG_HOME=" abs_srcdir "/virshtestdata/uriprecedence-xdg/good/", NULL, }; info.testname = "uriprecedence-xdg-config"; info.env = myenv; info.forbid_root = true; DO_TEST_INFO(&info); } /* all other tests don't care */ info.forbid_root = false; /* test 2 - LIBVIRT_DEFAULT_URI env variable */ { const char *myenv[] = { xdgDirBad, "LIBVIRT_DEFAULT_URI=test:///default?good_uri", NULL, }; info.testname = "uriprecedence-LIBVIRT_DEFAULT_URI"; info.env = myenv; DO_TEST_INFO(&info); } /* test 3 - VIRSH_DEFAULT_CONNECT_URI env variable */ { const char *myenv[] = { xdgDirBad, "LIBVIRT_DEFAULT_URI=test:///default?bad_uri", "VIRSH_DEFAULT_CONNECT_URI=test:///default?good_uri", NULL, }; info.testname = "uriprecedence-VIRSH_DEFAULT_CONNECT_URI"; info.env = myenv; DO_TEST_INFO(&info); } /* test 3 - --connect parameter */ { const char *myenv[] = { xdgDirBad, "LIBVIRT_DEFAULT_URI=test:///default?bad_uri", "VIRSH_DEFAULT_CONNECT_URI=test:///default?bad_uri", NULL, }; const char *myargv[] = { abs_top_builddir "/tools/virsh", "--connect", "test:///default?good_uri", uriTest, NULL, }; info.testname = "uriprecedence-param"; info.env = myenv; info.argv = myargv; DO_TEST_INFO(&info); } } VIR_FREE(custom_uri); return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } VIR_TEST_MAIN(mymain) #endif /* WIN32 */