From 008be5dbfa4df03a6ead8a07cf40d170efd21455 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 12 Apr 2013 13:27:08 +0100 Subject: [PATCH] tests: More flexible re-implementation of C API tests. Instead of using the various 'TestOutput', 'TestOutputList' etc macros, it makes better sense to let the tests contain fragments of C code to perform the checks. Several new macros are added: - 'TestResult': This macro takes a C expression which is evaluated to test the result of commands. For example to compare if a string result has some value: TestResult (* command sequence *), "STREQ (ret, \"abcd\")" The variable "ret" contains the result from the last command in the sequence. But also, variables "ret1", "ret2", etc contain the results from the last but one command, the command before that, and so forth, allowing much more flexible tests to be written. - 'TestResultTrue', 'TestResultFalse': Wrappers that test the last command for true/false. --- generator/tests_c_api.ml | 804 ++++++++++++++++++++------------------- generator/types.ml | 95 ++--- generator/utils.ml | 8 +- tests/c-api/Makefile.am | 3 + 4 files changed, 457 insertions(+), 453 deletions(-) diff --git a/generator/tests_c_api.ml b/generator/tests_c_api.ml index 6b56a6454..3623a71d6 100644 --- a/generator/tests_c_api.ml +++ b/generator/tests_c_api.ml @@ -37,10 +37,12 @@ let rec generate_tests () = #include #include +#include #include #include #include #include +#include #include \"guestfs.h\" #include \"guestfs-internal-frontend.h\" @@ -57,19 +59,6 @@ print_strings (char *const *argv) printf (\"\\t%%s\\n\", argv[argc]); } -static int -is_available (const char *group) -{ - const char *groups[] = { group, NULL }; - int r; - - guestfs_push_error_handler (g, NULL, NULL); - r = guestfs_available (g, (char **) groups); - guestfs_pop_error_handler (g); - - return r == 0; -} - static void incr (guestfs_h *g, void *iv) { @@ -124,6 +113,22 @@ next_test (guestfs_h *g, size_t test_num, size_t nr_tests, printf (\"%%3zu/%%3zu %%s\\n\", test_num, nr_tests, test_name); } +static void +skipped (const char *test_name, const char *fs, ...) +{ + va_list args; + CLEANUP_FREE char *reason = NULL; + int len; + + va_start (args, fs); + len = vasprintf (&reason, fs, args); + va_end (args); + assert (len >= 0); + + printf (\" %%s skipped (reason: %%s)\\n\", + test_name, reason); +} + "; (* Generate a list of commands which are not tested anywhere. *) @@ -351,7 +356,7 @@ static int %s (void) { if (%s_skip ()) { - printf (\" %%s skipped (reason: environment variable set)\\n\", \"%s\"); + skipped (\"%s\", \"environment variable set\"); return 0; } @@ -360,27 +365,31 @@ static int (* Optional functions should only be tested if the relevant * support is available in the daemon. *) - (match optional with - | Some group -> - pr " if (!is_available (\"%s\")) {\n" group; - pr " printf (\" %%s skipped (reason: group %%s not available in daemon)\\n\", \"%s\", \"%s\");\n" test_name group; + let group_test group = + let sym = gensym "features" in + pr " const char *%s[] = { \"%s\", NULL };\n" sym group; + pr " if (!guestfs_feature_available (g, (char **) %s)) {\n" sym; + pr " skipped (\"%s\", \"group %%s not available in daemon\",\n" + test_name; + pr " %s[0]);\n" sym; pr " return 0;\n"; pr " }\n"; + pr "\n" + in + + (match optional with + | Some group -> group_test group | None -> () ); (match prereq with | Disabled -> - pr " printf (\" %%s skipped (reason: test disabled in generator)\\n\", \"%s\");\n" test_name + pr " skipped (\"%s\", \"test disabled in generator\");\n" test_name | IfAvailable group -> - pr " if (!is_available (\"%s\")) {\n" group; - pr " printf (\" %%s skipped (reason: %%s not available)\\n\", \"%s\", \"%s\");\n" test_name group; - pr " return 0;\n"; - pr " }\n"; - pr "\n"; - generate_one_test_body name i test_name init test; + group_test group; + generate_one_test_body name i test_name init test; | Always -> - generate_one_test_body name i test_name init test + generate_one_test_body name i test_name init test ); pr " return 0;\n"; @@ -453,6 +462,8 @@ and generate_one_test_body name i test_name init test = ["mount"; "/dev/sdb1"; "/"]] ); + pr "\n"; + let get_seq_last = function | [] -> failwithf "%s: you cannot use [] (empty list) when expecting a command" @@ -464,201 +475,241 @@ and generate_one_test_body name i test_name init test = match test with | TestRun seq -> - pr " /* TestRun for %s (%d) */\n" name i; - List.iter (generate_test_command_call test_name) seq + pr " /* TestRun for %s (%d) */\n" name i; + List.iter (generate_test_command_call test_name) seq + + | TestResult (seq, expr) -> + pr " /* TestResult for %s (%d) */\n" name i; + let n = List.length seq in + iteri ( + fun i cmd -> + let ret = if i = n-1 then "ret" else sprintf "ret%d" (n-i-1) in + generate_test_command_call ~ret test_name cmd + ) seq; + pr " if (! (%s)) {\n" expr; + pr " fprintf (stderr, \"%%s: test failed: expression false: %%s\\n\",\n"; + pr " \"%s\", \"%s\");\n" test_name expr; + pr " if (!guestfs_get_trace (g))\n"; + pr " fprintf (stderr, \"Set LIBGUESTFS_TRACE=1 to see values returned from API calls.\\n\");\n"; + pr " return -1;\n"; + pr " }\n" + + | TestResultTrue seq -> + pr " /* TestResultTrue for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call test_name ~ret:"ret" last; + pr " if (!ret) {\n"; + pr " fprintf (stderr, \"%%s: test failed: expected last command %%s to return 'true' but it returned 'false'\\n\",\n"; + pr " \"%s\", \"%s\");\n" test_name (List.hd last); + pr " return -1;\n"; + pr " }\n" + + | TestResultFalse seq -> + pr " /* TestResultTrue for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call test_name ~ret:"ret" last; + pr " if (ret) {\n"; + pr " fprintf (stderr, \"%%s: test failed: expected last command %%s to return 'false' but it returned 'true'\\n\",\n"; + pr " \"%s\", \"%s\");\n" test_name (List.hd last); + pr " return -1;\n"; + pr " }\n" + + | TestLastFail seq -> + pr " /* TestLastFail for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call test_name ~expect_error:true last + + (* Backwards compatible ... *) + | TestOutput (seq, expected) -> pr " /* TestOutput for %s (%d) */\n" name i; - pr " const char *expected = \"%s\";\n" (c_quote expected); let seq, last = get_seq_last seq in - let test () = - pr " if (STRNEQ (r, expected)) {\n"; - pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", expected, r);\n" test_name; - pr " return -1;\n"; - pr " }\n" + let test ret = + pr " if (STRNEQ (%s, \"%s\")) {\n" ret (c_quote expected); + pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", \"%s\", %s);\n" test_name (c_quote expected) ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputList (seq, expected) -> pr " /* TestOutputList for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = + let test ret = iteri ( fun i str -> - pr " if (!r[%d]) {\n" i; - pr " fprintf (stderr, \"%%s: short list returned from command\\n\", \"%s\");\n" test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n"; - pr " {\n"; - pr " const char *expected = \"%s\";\n" (c_quote str); - pr " if (STRNEQ (r[%d], expected)) {\n" i; - pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", expected, r[%d]);\n" test_name i; - pr " return -1;\n"; - pr " }\n"; - pr " }\n" + pr " if (!%s[%d]) {\n" ret i; + pr " fprintf (stderr, \"%%s: short list returned from command\\n\", \"%s\");\n" test_name; + pr " print_strings (%s);\n" ret; + pr " return -1;\n"; + pr " }\n"; + pr " if (STRNEQ (%s[%d], \"%s\")) {\n" ret i (c_quote str); + pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", \"%s\", %s[%d]);\n" test_name (c_quote str) ret i; + pr " return -1;\n"; + pr " }\n"; ) expected; - pr " if (r[%d] != NULL) {\n" (List.length expected); - pr " fprintf (stderr, \"%%s: extra elements returned from command\\n\", \"%s\");\n" test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n" + pr " if (%s[%d] != NULL) {\n" ret (List.length expected); + pr " fprintf (stderr, \"%%s: extra elements returned from command\\n\", \"%s\");\n" test_name; + pr " print_strings (%s);\n" ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputListOfDevices (seq, expected) -> pr " /* TestOutputListOfDevices for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = + let test ret = iteri ( fun i str -> - pr " if (!r[%d]) {\n" i; - pr " fprintf (stderr, \"%%s: short list returned from command\\n\", \"%s\");\n" test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n"; - pr " {\n"; - pr " const char *expected = \"%s\";\n" (c_quote str); - pr " r[%d][5] = 's';\n" i; - pr " if (STRNEQ (r[%d], expected)) {\n" i; - pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", expected, r[%d]);\n" test_name i; - pr " return -1;\n"; - pr " }\n"; - pr " }\n" + pr " if (!%s[%d]) {\n" ret i; + pr " fprintf (stderr, \"%%s: short list returned from command\\n\", \"%s\");\n" test_name; + pr " print_strings (%s);\n" ret; + pr " return -1;\n"; + pr " }\n"; + pr " %s[%d][5] = 's';\n" ret i; + pr " if (STRNEQ (%s[%d], \"%s\")) {\n" ret i (c_quote str); + pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", \"%s\", %s[%d]);\n" test_name (c_quote str) ret i; + pr " return -1;\n"; + pr " }\n"; ) expected; - pr " if (r[%d] != NULL) {\n" (List.length expected); - pr " fprintf (stderr, \"%%s: extra elements returned from command\\n\", \"%s\");\n" test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n" + pr " if (%s[%d] != NULL) {\n" ret (List.length expected); + pr " fprintf (stderr, \"%%s: extra elements returned from command\\n\", \"%s\");\n" test_name; + pr " print_strings (%s);\n" ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputInt (seq, expected) -> pr " /* TestOutputInt for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = - pr " if (r != %d) {\n" expected; - pr " fprintf (stderr, \"%%s: expected %d but got %%d\\n\"," + let test ret = + pr " if (%s != %d) {\n" ret expected; + pr " fprintf (stderr, \"%%s: expected %d but got %%d\\n\",\n" expected; - pr " \"%s\", (int) r);\n" test_name; - pr " return -1;\n"; - pr " }\n" + pr " \"%s\", (int) %s);\n" test_name ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputIntOp (seq, op, expected) -> pr " /* TestOutputIntOp for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = - pr " if (! (r %s %d)) {\n" op expected; - pr " fprintf (stderr, \"%%s: expected %s %d but got %%d\\n\"," + let test ret = + pr " if (! (%s %s %d)) {\n" ret op expected; + pr " fprintf (stderr, \"%%s: expected %s %d but got %%d\\n\",\n" op expected; - pr " \"%s\", (int) r);\n" test_name; - pr " return -1;\n"; - pr " }\n" + pr " \"%s\", (int) %s);\n" test_name ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputTrue seq -> pr " /* TestOutputTrue for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = - pr " if (!r) {\n"; - pr " fprintf (stderr, \"%%s: expected true, got false\\n\", \"%s\");\n" test_name; - pr " return -1;\n"; - pr " }\n" + let test ret = + pr " if (!%s) {\n" ret; + pr " fprintf (stderr, \"%%s: expected true, got false\\n\", \"%s\");\n" test_name; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputFalse seq -> pr " /* TestOutputFalse for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = - pr " if (r) {\n"; - pr " fprintf (stderr, \"%%s: expected false, got true\\n\", \"%s\");\n" test_name; - pr " return -1;\n"; - pr " }\n" + let test ret = + pr " if (%s) {\n" ret; + pr " fprintf (stderr, \"%%s: expected false, got true\\n\", \"%s\");\n" test_name; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputLength (seq, expected) -> pr " /* TestOutputLength for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = - pr " int j;\n"; - pr " for (j = 0; j < %d; ++j)\n" expected; - pr " if (r[j] == NULL) {\n"; - pr " fprintf (stderr, \"%%s: short list returned\\n\", \"%s\");\n" test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n"; - pr " if (r[j] != NULL) {\n"; - pr " fprintf (stderr, \"%%s: long list returned\\n\", \"%s\");\n" - test_name; - pr " print_strings (r);\n"; + let test ret = + pr " int j;\n"; + pr " for (j = 0; j < %d; ++j)\n" expected; + pr " if (%s[j] == NULL) {\n" ret; + pr " fprintf (stderr, \"%%s: short list returned\\n\", \"%s\");\n" test_name; + pr " print_strings (%s);\n" ret; pr " return -1;\n"; - pr " }\n" + pr " }\n"; + pr " if (%s[j] != NULL) {\n" ret; + pr " fprintf (stderr, \"%%s: long list returned\\n\", \"%s\");\n" + test_name; + pr " print_strings (%s);\n" ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputBuffer (seq, expected) -> pr " /* TestOutputBuffer for %s (%d) */\n" name i; - pr " const char *expected = \"%s\";\n" (c_quote expected); let seq, last = get_seq_last seq in let len = String.length expected in - let test () = - pr " if (size != %d) {\n" len; - pr " fprintf (stderr, \"%%s: returned size of buffer wrong, expected %d but got %%zu\\n\", \"%s\", size);\n" len test_name; - pr " return -1;\n"; - pr " }\n"; - pr " if (STRNEQLEN (r, expected, size)) {\n"; - pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", expected, r);\n" test_name; - pr " return -1;\n"; - pr " }\n" + let test ret = + pr " if (size != %d) {\n" len; + pr " fprintf (stderr, \"%%s: returned size of buffer wrong, expected %d but got %%zu\\n\", \"%s\", size);\n" len test_name; + pr " return -1;\n"; + pr " }\n"; + pr " if (STRNEQLEN (%s, \"%s\", size)) {\n" ret (c_quote expected); + pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", \"%s\", %s);\n" test_name (c_quote expected) ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputStruct (seq, checks) -> pr " /* TestOutputStruct for %s (%d) */\n" name i; let seq, last = get_seq_last seq in - let test () = + let test ret = List.iter ( function | CompareWithInt (field, expected) -> - pr " if (r->%s != %d) {\n" field expected; - pr " fprintf (stderr, \"%%s: %s was %%d, expected %d\\n\",\n" + pr " if (%s->%s != %d) {\n" ret field expected; + pr " fprintf (stderr, \"%%s: %s was %%d, expected %d\\n\",\n" field expected; - pr " \"%s\", (int) r->%s);\n" test_name field; - pr " return -1;\n"; - pr " }\n" + pr " \"%s\", (int) %s->%s);\n" test_name ret field; + pr " return -1;\n"; + pr " }\n" | CompareWithIntOp (field, op, expected) -> - pr " if (!(r->%s %s %d)) {\n" field op expected; - pr " fprintf (stderr, \"%%s: %s was %%d, expected %s %d\\n\",\n" + pr " if (!(%s->%s %s %d)) {\n" ret field op expected; + pr " fprintf (stderr, \"%%s: %s was %%d, expected %s %d\\n\",\n" field op expected; - pr " \"%s\", (int) r->%s);\n" test_name field; - pr " return -1;\n"; - pr " }\n" + pr " \"%s\", (int) %s->%s);\n" test_name ret field; + pr " return -1;\n"; + pr " }\n" | CompareWithString (field, expected) -> - pr " if (STRNEQ (r->%s, \"%s\")) {\n" field expected; - pr " fprintf (stderr, \"%%s: %s was \\\"%%s\\\", expected \\\"%s\\\"\\n\",\n" + pr " if (STRNEQ (%s->%s, \"%s\")) {\n" ret field expected; + pr " fprintf (stderr, \"%%s: %s was \\\"%%s\\\", expected \\\"%s\\\"\\n\",\n" field expected; - pr " \"%s\", r->%s);\n" test_name field; - pr " return -1;\n"; - pr " }\n" + pr " \"%s\", %s->%s);\n" test_name ret field; + pr " return -1;\n"; + pr " }\n" | CompareFieldsIntEq (field1, field2) -> - pr " if (r->%s != r->%s) {\n" field1 field2; - pr " fprintf (stderr, \"%s: %s (%%d) <> %s (%%d)\\n\",\n" + pr " if (%s->%s != r->%s) {\n" ret field1 field2; + pr " fprintf (stderr, \"%s: %s (%%d) <> %s (%%d)\\n\",\n" test_name field1 field2; - pr " (int) r->%s, (int) r->%s);\n" field1 field2; - pr " return -1;\n"; - pr " }\n" + pr " (int) %s->%s, (int) %s->%s);\n" + ret field1 ret field2; + pr " return -1;\n"; + pr " }\n" | CompareFieldsStrEq (field1, field2) -> - pr " if (STRNEQ (r->%s, r->%s)) {\n" field1 field2; - pr " fprintf (stderr, \"%s: %s (\"%%s\") <> %s (\"%%s\")\\n\",\n" + pr " if (STRNEQ (%s->%s, r->%s)) {\n" ret field1 field2; + pr " fprintf (stderr, \"%s: %s (\"%%s\") <> %s (\"%%s\")\\n\",\n" test_name field1 field2; - pr " r->%s, r->%s);\n" field1 field2; - pr " return -1;\n"; - pr " }\n" + pr " %s->%s, %s->%s);\n" ret field1 ret field2; + pr " return -1;\n"; + pr " }\n" ) checks in List.iter (generate_test_command_call test_name) seq; @@ -668,24 +719,23 @@ and generate_one_test_body name i test_name init test = pr " char expected[33];\n"; pr " md5sum (\"%s\", expected);\n" filename; let seq, last = get_seq_last seq in - let test () = - pr " if (STRNEQ (r, expected)) {\n"; - pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", expected, r);\n" test_name; - pr " return -1;\n"; - pr " }\n" + let test ret = + pr " if (STRNEQ (%s, expected)) {\n" ret; + pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", expected, %s);\n" test_name ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last | TestOutputDevice (seq, expected) -> pr " /* TestOutputDevice for %s (%d) */\n" name i; - pr " const char *expected = \"%s\";\n" (c_quote expected); let seq, last = get_seq_last seq in - let test () = - pr " r[5] = 's';\n"; - pr " if (STRNEQ (r, expected)) {\n"; - pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", expected, r);\n" test_name; - pr " return -1;\n"; - pr " }\n" + let test ret = + pr " %s[5] = 's';\n" ret; + pr " if (STRNEQ (%s, \"%s\")) {\n" ret (c_quote expected); + pr " fprintf (stderr, \"%%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", \"%s\", %s);\n" test_name (c_quote expected) ret; + pr " return -1;\n"; + pr " }\n" in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last @@ -693,258 +743,250 @@ and generate_one_test_body name i test_name init test = pr " /* TestOutputHashtable for %s (%d) */\n" name i; pr " const char *key, *expected, *value;\n"; let seq, last = get_seq_last seq in - let test () = + let test ret = List.iter ( fun (key, value) -> - pr " key = \"%s\";\n" (c_quote key); - pr " expected = \"%s\";\n" (c_quote value); - pr " value = get_key (r, key);\n"; - pr " if (value == NULL) {\n"; - pr " fprintf (stderr, \"%%s: key \\\"%%s\\\" not found in hash: expecting \\\"%%s\\\"\\n\", \"%s\", key, expected);\n" test_name; - pr " return -1;\n"; - pr " }\n"; - pr " if (STRNEQ (value, expected)) {\n"; - pr " fprintf (stderr, \"%%s: key \\\"%%s\\\": expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", key, expected, value);\n" test_name; - pr " return -1;\n"; - pr " }\n"; + pr " key = \"%s\";\n" (c_quote key); + pr " expected = \"%s\";\n" (c_quote value); + pr " value = get_key (%s, key);\n" ret; + pr " if (value == NULL) {\n"; + pr " fprintf (stderr, \"%%s: key \\\"%%s\\\" not found in hash: expecting \\\"%%s\\\"\\n\", \"%s\", key, expected);\n" test_name; + pr " return -1;\n"; + pr " }\n"; + pr " if (STRNEQ (value, expected)) {\n"; + pr " fprintf (stderr, \"%%s: key \\\"%%s\\\": expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", \"%s\", key, expected, value);\n" test_name; + pr " return -1;\n"; + pr " }\n"; ) fields in List.iter (generate_test_command_call test_name) seq; generate_test_command_call ~test test_name last - | TestLastFail seq -> - pr " /* TestLastFail for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - List.iter (generate_test_command_call test_name) seq; - generate_test_command_call test_name ~expect_error:true last -(* Generate the code to run a command, leaving the result in 'r'. - * If you expect to get an error then you should set expect_error:true. +(* Generate the code to run a command, leaving the result in the C + * variable named 'ret'. If you expect to get an error then you should + * set expect_error:true. *) -and generate_test_command_call ?(expect_error = false) ?test test_name cmd = - match cmd with - | [] -> assert false - | name :: args -> - (* Look up the function. *) - let f = - try List.find (fun { name = n } -> n = name) all_functions - with Not_found -> - failwithf "%s: in test, command %s was not found" test_name name in +and generate_test_command_call ?(expect_error = false) ?test ?ret test_name cmd= + let ret = match ret with Some ret -> ret | None -> gensym "ret" in - (* Look up the arguments and return type. *) - let style_ret, style_args, style_optargs = f.style in + let name, args = + match cmd with [] -> assert false | name :: args -> name, args in - (* Match up the arguments strings and argument types. *) - let args, optargs = - let rec loop argts args = - match argts, args with - | (t::ts), (s::ss) -> - let args, rest = loop ts ss in - ((t, s) :: args), rest - | [], ss -> [], ss - | ts, [] -> - failwithf "%s: in test, too few args given to function %s" - test_name name - in - let args, optargs = loop style_args args in - let optargs, rest = loop style_optargs optargs in - if rest <> [] then - failwithf "%s: in test, too many args given to function %s" - test_name name; - args, optargs in + (* Look up the function. *) + let f = + try List.find (fun { name = n } -> n = name) all_functions + with Not_found -> + failwithf "%s: in test, command %s was not found" test_name name in - pr " {\n"; + (* Look up the arguments and return type. *) + let style_ret, style_args, style_optargs = f.style in - List.iter ( - function - | OptString n, "NULL" -> () - | Pathname n, arg - | Device n, arg - | Mountable n, arg - | Dev_or_Path n, arg | Mountable_or_Path n, arg - | String n, arg - | OptString n, arg - | Key n, arg -> - pr " const char *%s = \"%s\";\n" n (c_quote arg); - | BufferIn n, arg -> - pr " const char *%s = \"%s\";\n" n (c_quote arg); - pr " size_t %s_size = %d;\n" n (String.length arg) - | Int _, _ - | Int64 _, _ - | Bool _, _ - | FileIn _, _ | FileOut _, _ -> () - | StringList n, "" | DeviceList n, "" -> - pr " const char *const %s[1] = { NULL };\n" n - | StringList n, arg | DeviceList n, arg -> + (* Match up the arguments strings and argument types. *) + let args, optargs = + let rec loop argts args = + match argts, args with + | (t::ts), (s::ss) -> + let args, rest = loop ts ss in + ((t, s) :: args), rest + | [], ss -> [], ss + | ts, [] -> + failwithf "%s: in test, too few args given to function %s" + test_name name + in + let args, optargs = loop style_args args in + let optargs, rest = loop style_optargs optargs in + if rest <> [] then + failwithf "%s: in test, too many args given to function %s" + test_name name; + args, optargs in + + (* Generate a new symbol for each arg, and one for optargs. *) + let args = List.map (fun (arg, value) -> arg, value, gensym "arg") args in + let optargs_sym = gensym "optargs" in + + List.iter ( + function + | OptString _, "NULL", _ -> () + | Pathname _, arg, sym + | Device _, arg, sym + | Mountable _, arg, sym + | Dev_or_Path _, arg, sym + | Mountable_or_Path _, arg, sym + | String _, arg, sym + | OptString _, arg, sym + | Key _, arg, sym -> + pr " const char *%s = \"%s\";\n" sym (c_quote arg); + | BufferIn _, arg, sym -> + pr " const char *%s = \"%s\";\n" sym (c_quote arg); + pr " size_t %s_size = %d;\n" sym (String.length arg) + | Int _, _, _ + | Int64 _, _, _ + | Bool _, _, _ + | FileIn _, _, _ + | FileOut _, _, _ -> () + | StringList _, "", sym + | DeviceList _, "", sym -> + pr " const char *const %s[1] = { NULL };\n" sym + | StringList _, arg, sym + | DeviceList _, arg, sym -> + let strs = string_split " " arg in + iteri ( + fun i str -> + pr " const char *%s_%d = \"%s\";\n" sym i (c_quote str); + ) strs; + pr " const char *const %s[] = {\n" sym; + iteri ( + fun i _ -> pr " %s_%d,\n" sym i + ) strs; + pr " NULL\n"; + pr " };\n"; + | Pointer _, _, _ -> + (* Difficult to make these pointers in order to run a test. *) + assert false + ) args; + + if optargs <> [] then ( + pr " struct %s %s;\n" f.c_function optargs_sym; + let _, bitmask = List.fold_left ( + fun (shift, bitmask) optarg -> + let is_set = + match optarg with + | OBool n, "" -> false + | OBool n, "true" -> + pr " %s.%s = 1;\n" optargs_sym n; true + | OBool n, "false" -> + pr " %s.%s = 0;\n" optargs_sym n; true + | OBool n, arg -> + failwithf "boolean optional arg '%s' should be empty string or \"true\" or \"false\"" n + | OInt n, "" -> false + | OInt n, i -> + let i = + try int_of_string i + with Failure _ -> failwithf "integer optional arg '%s' should be empty string or number" n in + pr " %s.%s = %d;\n" optargs_sym n i; true + | OInt64 n, "" -> false + | OInt64 n, i -> + let i = + try Int64.of_string i + with Failure _ -> failwithf "int64 optional arg '%s' should be empty string or number" n in + pr " %s.%s = %Ld;\n" optargs_sym n i; true + | OString n, "NOARG" -> false + | OString n, arg -> + pr " %s.%s = \"%s\";\n" optargs_sym n (c_quote arg); true + | OStringList n, "NOARG" -> false + | OStringList n, "" -> + pr " const char *const %s[1] = { NULL };\n" n; true + | OStringList n, arg -> let strs = string_split " " arg in iteri ( fun i str -> - pr " const char *%s_%d = \"%s\";\n" n i (c_quote str); + pr " const char *%s_%d = \"%s\";\n" n i (c_quote str); ) strs; - pr " const char *const %s[] = {\n" n; + pr " const char *const %s[] = {\n" n; iteri ( - fun i _ -> pr " %s_%d,\n" n i + fun i _ -> pr " %s_%d,\n" n i ) strs; - pr " NULL\n"; - pr " };\n"; - | Pointer _, _ -> - (* Difficult to make these pointers in order to run a test. *) - assert false - ) args; + pr " NULL\n"; + pr " };\n"; true in + let bit = if is_set then Int64.shift_left 1L shift else 0L in + let bitmask = Int64.logor bitmask bit in + let shift = shift + 1 in + (shift, bitmask) + ) (0, 0L) optargs in + pr " %s.bitmask = UINT64_C(0x%Lx);\n" optargs_sym bitmask; + ); - if optargs <> [] then ( - pr " struct %s optargs;\n" f.c_function; - let _, bitmask = List.fold_left ( - fun (shift, bitmask) optarg -> - let is_set = - match optarg with - | OBool n, "" -> false - | OBool n, "true" -> - pr " optargs.%s = 1;\n" n; true - | OBool n, "false" -> - pr " optargs.%s = 0;\n" n; true - | OBool n, arg -> - failwithf "boolean optional arg '%s' should be empty string or \"true\" or \"false\"" n - | OInt n, "" -> false - | OInt n, i -> - let i = - try int_of_string i - with Failure _ -> failwithf "integer optional arg '%s' should be empty string or number" n in - pr " optargs.%s = %d;\n" n i; true - | OInt64 n, "" -> false - | OInt64 n, i -> - let i = - try Int64.of_string i - with Failure _ -> failwithf "int64 optional arg '%s' should be empty string or number" n in - pr " optargs.%s = %Ld;\n" n i; true - | OString n, "NOARG" -> false - | OString n, arg -> - pr " optargs.%s = \"%s\";\n" n (c_quote arg); true - | OStringList n, "NOARG" -> false - | OStringList n, "" -> - pr " const char *const %s[1] = { NULL };\n" n; true - | OStringList n, arg -> - let strs = string_split " " arg in - iteri ( - fun i str -> - pr " const char *%s_%d = \"%s\";\n" n i (c_quote str); - ) strs; - pr " const char *const %s[] = {\n" n; - iteri ( - fun i _ -> pr " %s_%d,\n" n i - ) strs; - pr " NULL\n"; - pr " };\n"; true in - let bit = if is_set then Int64.shift_left 1L shift else 0L in - let bitmask = Int64.logor bitmask bit in - let shift = shift + 1 in - (shift, bitmask) - ) (0, 0L) optargs in - pr " optargs.bitmask = UINT64_C(0x%Lx);\n" bitmask; - ); + (match style_ret with + | RErr | RInt _ | RBool _ -> pr " int %s;\n" ret + | RInt64 _ -> pr " int64_t %s;\n" ret + | RConstString _ | RConstOptString _ -> + pr " const char *%s;\n" ret + | RString _ -> + pr " CLEANUP_FREE char *%s;\n" ret + | RStringList _ | RHashtable _ -> + pr " CLEANUP_FREE char **%s;\n" ret; + | RStruct (_, typ) -> + pr " CLEANUP_FREE_%s struct guestfs_%s *%s;\n" + (String.uppercase typ) typ ret + | RStructList (_, typ) -> + pr " CLEANUP_FREE_%s_LIST struct guestfs_%s_list *%s;\n" + (String.uppercase typ) typ ret + | RBufferOut _ -> + pr " CLEANUP_FREE char *%s;\n" ret; + pr " size_t size;\n" + ); - (match style_ret with - | RErr | RInt _ | RBool _ -> pr " int r;\n" - | RInt64 _ -> pr " int64_t r;\n" - | RConstString _ | RConstOptString _ -> - pr " const char *r;\n" - | RString _ -> pr " char *r;\n" - | RStringList _ | RHashtable _ -> - pr " char **r;\n"; - pr " size_t i;\n" - | RStruct (_, typ) -> - pr " struct guestfs_%s *r;\n" typ - | RStructList (_, typ) -> - pr " struct guestfs_%s_list *r;\n" typ - | RBufferOut _ -> - pr " char *r;\n"; - pr " size_t size;\n" - ); + if expect_error then + pr " guestfs_push_error_handler (g, NULL, NULL);\n"; + pr " %s = %s (g" ret f.c_function; - if expect_error then - pr " guestfs_push_error_handler (g, NULL, NULL);\n"; - pr " r = %s (g" f.c_function; + (* Generate the parameters. *) + List.iter ( + function + | OptString _, "NULL", _ -> pr ", NULL" + | Pathname _, _, sym + | Device _, _, sym + | Mountable _, _, sym + | Dev_or_Path _, _, sym + | Mountable_or_Path _, _, sym + | String _, _, sym + | OptString _, _, sym + | Key _, _, sym -> pr ", %s" sym + | BufferIn _, _, sym -> pr ", %s, %s_size" sym sym + | FileIn _, arg, _ + | FileOut _, arg, _ -> pr ", \"%s\"" (c_quote arg) + | StringList _, _, sym | DeviceList _, _, sym -> pr ", (char **) %s" sym + | Int _, arg, _ -> + let i = + try int_of_string arg + with Failure "int_of_string" -> + failwithf "%s: expecting an int, but got '%s'" test_name arg in + pr ", %d" i + | Int64 _, arg, _ -> + let i = + try Int64.of_string arg + with Failure "int_of_string" -> + failwithf "%s: expecting an int64, but got '%s'" test_name arg in + pr ", %Ld" i + | Bool _, arg, _ -> + let b = bool_of_string arg in pr ", %d" (if b then 1 else 0) + | Pointer _, _, _ -> assert false + ) args; - (* Generate the parameters. *) - List.iter ( - function - | OptString _, "NULL" -> pr ", NULL" - | Pathname n, _ - | Device n, _ | Mountable n, _ - | Dev_or_Path n, _ | Mountable_or_Path n, _ - | String n, _ - | OptString n, _ - | Key n, _ -> - pr ", %s" n - | BufferIn n, _ -> - pr ", %s, %s_size" n n - | FileIn _, arg | FileOut _, arg -> - pr ", \"%s\"" (c_quote arg) - | StringList n, _ | DeviceList n, _ -> - pr ", (char **) %s" n - | Int _, arg -> - let i = - try int_of_string arg - with Failure "int_of_string" -> - failwithf "%s: expecting an int, but got '%s'" test_name arg in - pr ", %d" i - | Int64 _, arg -> - let i = - try Int64.of_string arg - with Failure "int_of_string" -> - failwithf "%s: expecting an int64, but got '%s'" test_name arg in - pr ", %Ld" i - | Bool _, arg -> - let b = bool_of_string arg in pr ", %d" (if b then 1 else 0) - | Pointer _, _ -> assert false - ) args; + (match style_ret with + | RBufferOut _ -> pr ", &size" + | _ -> () + ); - (match style_ret with - | RBufferOut _ -> pr ", &size" - | _ -> () - ); + if optargs <> [] then + pr ", &%s" optargs_sym; - if optargs <> [] then - pr ", &optargs"; + pr ");\n"; - pr ");\n"; + if expect_error then + pr " guestfs_pop_error_handler (g);\n"; - if expect_error then - pr " guestfs_pop_error_handler (g);\n"; + (match errcode_of_ret style_ret, expect_error with + | `CannotReturnError, _ -> () + | `ErrorIsMinusOne, false -> + pr " if (%s == -1)\n" ret; + pr " return -1;\n"; + | `ErrorIsMinusOne, true -> + pr " if (%s != -1)\n" ret; + pr " return -1;\n"; + | `ErrorIsNULL, false -> + pr " if (%s == NULL)\n" ret; + pr " return -1;\n"; + | `ErrorIsNULL, true -> + pr " if (%s != NULL)\n" ret; + pr " return -1;\n"; + ); - (match errcode_of_ret style_ret, expect_error with - | `CannotReturnError, _ -> () - | `ErrorIsMinusOne, false -> - pr " if (r == -1)\n"; - pr " return -1;\n"; - | `ErrorIsMinusOne, true -> - pr " if (r != -1)\n"; - pr " return -1;\n"; - | `ErrorIsNULL, false -> - pr " if (r == NULL)\n"; - pr " return -1;\n"; - | `ErrorIsNULL, true -> - pr " if (r != NULL)\n"; - pr " return -1;\n"; - ); + (* Insert the test code. *) + (match test with + | None -> () + | Some f -> f ret + ) - (* Insert the test code. *) - (match test with - | None -> () - | Some f -> f () - ); - - (match style_ret with - | RErr | RInt _ | RInt64 _ | RBool _ - | RConstString _ | RConstOptString _ -> () - | RString _ | RBufferOut _ -> pr " free (r);\n" - | RStringList _ | RHashtable _ -> - pr " for (i = 0; r[i] != NULL; ++i)\n"; - pr " free (r[i]);\n"; - pr " free (r);\n" - | RStruct (_, typ) -> - pr " guestfs_free_%s (r);\n" typ - | RStructList (_, typ) -> - pr " guestfs_free_%s_list (r);\n" typ - ); - - pr " }\n" +and gensym prefix = + sprintf "_%s%d" prefix (unique ()) diff --git a/generator/types.ml b/generator/types.ml index 7eecb184f..cb50c27e1 100644 --- a/generator/types.ml +++ b/generator/types.ml @@ -207,82 +207,39 @@ and test = (* Run the command sequence and just expect nothing to fail. *) | TestRun of seq - (* Run the command sequence and expect the output of the final - * command to be the string. + (* Run the command sequence. No command should fail, and the + * output of the command(s) is tested using the C expression which + * should return true. + * + * In the C expression, 'ret' is the result of the final command, + * 'ret1' is the result of the last but one command, and so on + * backwards. *) - | TestOutput of seq * string - - (* Run the command sequence and expect the output of the final - * command to be the list of strings. - *) - | TestOutputList of seq * string list - - (* Run the command sequence and expect the output of the final - * command to be the list of block devices (could be either - * "/dev/sd.." or "/dev/hd.." form - we don't check the 5th - * character of each string). - *) - | TestOutputListOfDevices of seq * string list - - (* Run the command sequence and expect the output of the final - * command to be the integer. - *) - | TestOutputInt of seq * int - - (* Run the command sequence and expect the output of the final - * command to be , eg. ">=", "1". - *) - | TestOutputIntOp of seq * string * int - - (* Run the command sequence and expect the output of the final - * command to be a true value (!= 0 or != NULL). - *) - | TestOutputTrue of seq - - (* Run the command sequence and expect the output of the final - * command to be a false value (== 0 or == NULL, but not an error). - *) - | TestOutputFalse of seq - - (* Run the command sequence and expect the output of the final - * command to be a list of the given length (but don't care about - * content). - *) - | TestOutputLength of seq * int - - (* Run the command sequence and expect the output of the final - * command to be a buffer (RBufferOut), ie. string + size. - *) - | TestOutputBuffer of seq * string - - (* Run the command sequence and expect the output of the final - * command to be a structure. - *) - | TestOutputStruct of seq * test_field_compare list - - (* Run the command sequence and expect the output of the final - * command to be a string which is the hex MD5 of the content of - * the named file. - *) - | TestOutputFileMD5 of seq * string - - (* Run the command sequence and expect the output of the final - * command to be a string which is a block device name (we don't - * check the 5th character of the string, so "/dev/sda" == "/dev/vda"). - *) - | TestOutputDevice of seq * string - - (* Run the command sequence and expect a hashtable. Check - * one of more fields in the hashtable against known good - * strings. - *) - | TestOutputHashtable of seq * (string * string) list + | TestResult of seq * string (* Run the command sequence and expect the final command (only) * to fail. *) | TestLastFail of seq + (* The following are for backwards compatibility and will + * be replaced with 'TestResult'. + *) + | TestOutput of seq * string + | TestOutputList of seq * string list + | TestOutputListOfDevices of seq * string list + | TestOutputInt of seq * int + | TestOutputIntOp of seq * string * int + | TestOutputTrue of seq + | TestOutputFalse of seq + | TestOutputLength of seq * int + | TestOutputBuffer of seq * string + | TestOutputStruct of seq * test_field_compare list + | TestOutputFileMD5 of seq * string + | TestOutputDevice of seq * string + | TestOutputHashtable of seq * (string * string) list + +(* For backwards compatibility - will be removed. *) and test_field_compare = | CompareWithInt of string * int | CompareWithIntOp of string * string * int diff --git a/generator/utils.ml b/generator/utils.ml index b3b7ee379..33958aff3 100644 --- a/generator/utils.ml +++ b/generator/utils.ml @@ -259,7 +259,10 @@ let name_of_optargt = function | OBool n | OInt n | OInt64 n | OString n | OStringList n -> n let seq_of_test = function - | TestRun s | TestOutput (s, _) | TestOutputList (s, _) + | TestRun s + | TestResult (s, _) + | TestLastFail s + | TestOutput (s, _) | TestOutputList (s, _) | TestOutputListOfDevices (s, _) | TestOutputInt (s, _) | TestOutputIntOp (s, _, _) | TestOutputTrue s | TestOutputFalse s @@ -267,8 +270,7 @@ let seq_of_test = function | TestOutputStruct (s, _) | TestOutputFileMD5 (s, _) | TestOutputDevice (s, _) - | TestOutputHashtable (s, _) - | TestLastFail s -> s + | TestOutputHashtable (s, _) -> s let c_quote str = let str = replace_str str "\\" "\\\\" in diff --git a/tests/c-api/Makefile.am b/tests/c-api/Makefile.am index 03c90848a..c8f8c26fa 100644 --- a/tests/c-api/Makefile.am +++ b/tests/c-api/Makefile.am @@ -80,7 +80,10 @@ tests_CFLAGS = \ $(WARN_CFLAGS) $(WERROR_CFLAGS) \ $(GPROF_CFLAGS) $(GCOV_CFLAGS) tests_LDADD = \ + $(top_builddir)/src/libutils.la \ $(top_builddir)/src/libguestfs.la \ + $(LIBXML2_LIBS) \ + $(LIBVIRT_LIBS) \ $(top_builddir)/gnulib/lib/libgnu.la # This binary must be statically linked. It is used for testing