X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fgenerator.ml;h=f8e3934f13e29db044e8e9b77e91c671e4e04d4a;hp=9e17d6e4de9856c0984dfb13f0f4caaab4830d7f;hb=675411d34f807420d1b714436fc5a58fc0022dae;hpb=c41fe04a652437c920acb0e820762c53bf44a139 diff --git a/src/generator.ml b/src/generator.ml index 9e17d6e..f8e3934 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -130,22 +130,19 @@ can easily destroy all your data>." (* You can supply zero or as many tests as you want per API call. * * Note that the test environment has 3 block devices, of size 500MB, - * 50MB and 10MB (respectively /dev/sda, /dev/sdb, /dev/sdc). + * 50MB and 10MB (respectively /dev/sda, /dev/sdb, /dev/sdc), and + * a fourth squashfs block device with some known files on it (/dev/sdd). + * * Note for partitioning purposes, the 500MB device has 63 cylinders. * + * The squashfs block device (/dev/sdd) comes from images/test.sqsh. + * * To be able to run the tests in a reasonable amount of time, * the virtual machine and block devices are reused between tests. * So don't try testing kill_subprocess :-x * * Between each test we blockdev-setrw, umount-all, lvm-remove-all. * - * If the appliance is running an older Linux kernel (eg. RHEL 5) then - * devices are named /dev/hda etc. To cope with this, the test suite - * adds some hairly logic to detect this case, and then automagically - * replaces all strings which match "/dev/sd.*" with "/dev/hd.*". - * When writing test cases you shouldn't have to worry about this - * difference. - * * Don't assume anything about the previous contents of the block * devices. Use 'Init*' to create some initial scenarios. * @@ -173,6 +170,12 @@ and test = *) | 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 @@ -370,7 +373,12 @@ for whatever operations you want to perform (ie. read access if you just want to read the image or write access if you want to modify the image). -This is equivalent to the qemu parameter C<-drive file=filename>."); +This is equivalent to the qemu parameter C<-drive file=filename,cache=off>. + +Note that this call checks for the existence of C. This +stops you from specifying other types of drive which are supported +by qemu such as C and C URLs. To specify those, use +the general C call instead."); ("add_cdrom", (RErr, [String "filename"]), -1, [FishAlias "cdrom"], [], @@ -378,7 +386,33 @@ This is equivalent to the qemu parameter C<-drive file=filename>."); "\ This function adds a virtual CD-ROM disk image to the guest. -This is equivalent to the qemu parameter C<-cdrom filename>."); +This is equivalent to the qemu parameter C<-cdrom filename>. + +Note that this call checks for the existence of C. This +stops you from specifying other types of drive which are supported +by qemu such as C and C URLs. To specify those, use +the general C call instead."); + + ("add_drive_ro", (RErr, [String "filename"]), -1, [FishAlias "add-ro"], + [], + "add a drive in snapshot mode (read-only)", + "\ +This adds a drive in snapshot mode, making it effectively +read-only. + +Note that writes to the device are allowed, and will be seen for +the duration of the guestfs handle, but they are written +to a temporary file which is discarded as soon as the guestfs +handle is closed. We don't currently have any method to enable +changes to be committed, although qemu can support this. + +This is equivalent to the qemu parameter +C<-drive file=filename,snapshot=on>. + +Note that this call checks for the existence of C. This +stops you from specifying other types of drive which are supported +by qemu such as C and C URLs. To specify those, use +the general C call instead."); ("config", (RErr, [String "qemuparam"; OptString "qemuvalue"]), -1, [], [], @@ -658,8 +692,8 @@ This command is mostly useful for interactive sessions. Programs should probably use C instead."); ("list_devices", (RStringList "devices", []), 7, [], - [InitEmpty, Always, TestOutputList ( - [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"])], + [InitEmpty, Always, TestOutputListOfDevices ( + [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"; "/dev/sdd"])], "list the block devices", "\ List all the block devices. @@ -667,9 +701,9 @@ List all the block devices. The full block device names are returned, eg. C"); ("list_partitions", (RStringList "partitions", []), 8, [], - [InitBasicFS, Always, TestOutputList ( + [InitBasicFS, Always, TestOutputListOfDevices ( [["list_partitions"]], ["/dev/sda1"]); - InitEmpty, Always, TestOutputList ( + InitEmpty, Always, TestOutputListOfDevices ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["list_partitions"]], ["/dev/sda1"; "/dev/sda2"; "/dev/sda3"])], "list the partitions", @@ -682,9 +716,9 @@ This does not return logical volumes. For that you will need to call C."); ("pvs", (RStringList "physvols", []), 9, [], - [InitBasicFSonLVM, Always, TestOutputList ( + [InitBasicFSonLVM, Always, TestOutputListOfDevices ( [["pvs"]], ["/dev/sda1"]); - InitEmpty, Always, TestOutputList ( + InitEmpty, Always, TestOutputListOfDevices ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -1002,7 +1036,14 @@ Create a directory named C."); ["is_dir"; "/new/foo"]]; InitBasicFS, Always, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; - ["is_dir"; "/new"]]], + ["is_dir"; "/new"]]; + (* Regression tests for RHBZ#503133: *) + InitBasicFS, Always, TestRun + [["mkdir"; "/new"]; + ["mkdir_p"; "/new"]]; + InitBasicFS, Always, TestLastFail + [["touch"; "/new"]; + ["mkdir_p"; "/new"]]], "create a directory and parents", "\ Create a directory named C, creating any parent directories @@ -1070,7 +1111,7 @@ other objects like files. See also C."); ("pvcreate", (RErr, [String "device"]), 39, [], - [InitEmpty, Always, TestOutputList ( + [InitEmpty, Always, TestOutputListOfDevices ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -1193,7 +1234,7 @@ We hope to resolve this bug in a future version. In the meantime use C."); ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"], - [InitEmpty, Always, TestOutputList ( + [InitEmpty, Always, TestOutputListOfDevices ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mount"; "/dev/sda1"; "/"]; @@ -1211,7 +1252,7 @@ specified either by its mountpoint (path) or the device which contains the filesystem."); ("mounts", (RStringList "devices", []), 46, [], - [InitBasicFS, Always, TestOutputList ( + [InitBasicFS, Always, TestOutputListOfDevices ( [["mounts"]], ["/dev/sda1"])], "show mounted filesystems", "\ @@ -1329,7 +1370,9 @@ or compatible processor architecture). The single parameter is an argv-style list of arguments. The first element is the name of the program to run. Subsequent elements are parameters. The list must be -non-empty (ie. must contain a program name). +non-empty (ie. must contain a program name). Note that +the command runs directly, and is I invoked via +the shell (see C). The return value is anything printed to I by the command. @@ -1397,7 +1440,9 @@ locations."); "run a command, returning lines", "\ This is the same as C, but splits the -result into a list of lines."); +result into a list of lines. + +See also: C"); ("stat", (RStat "statbuf", [String "path"]), 52, [], [InitBasicFS, Always, TestOutputStruct ( @@ -1562,7 +1607,7 @@ This uses the L command."); ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [], [InitBasicFS, Always, TestOutput ( (* Pick a file from cwd which isn't likely to change. *) - [["upload"; "COPYING.LIB"; "/COPYING.LIB"]; + [["upload"; "../COPYING.LIB"; "/COPYING.LIB"]; ["checksum"; "md5"; "/COPYING.LIB"]], "e3eda01d9815f8d24aae2dbd89b68b06")], "upload a file from the local machine", "\ @@ -1576,7 +1621,7 @@ See also C."); ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [], [InitBasicFS, Always, TestOutput ( (* Pick a file from cwd which isn't likely to change. *) - [["upload"; "COPYING.LIB"; "/COPYING.LIB"]; + [["upload"; "../COPYING.LIB"; "/COPYING.LIB"]; ["download"; "/COPYING.LIB"; "testdownload.tmp"]; ["upload"; "testdownload.tmp"; "/upload"]; ["checksum"; "md5"; "/upload"]], "e3eda01d9815f8d24aae2dbd89b68b06")], @@ -1612,7 +1657,13 @@ See also C, C."); ["checksum"; "sha384"; "/new"]], "109bb6b5b6d5547c1ce03c7a8bd7d8f80c1cb0957f50c4f7fda04692079917e4f9cad52b878f3d8234e1a170b154b72d"); InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; - ["checksum"; "sha512"; "/new"]], "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123")], + ["checksum"; "sha512"; "/new"]], "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123"); + InitBasicFS, Always, TestOutput ( + (* RHEL 5 thinks this is an HFS+ filesystem unless we give + * the type explicitly. + *) + [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"]; + ["checksum"; "md5"; "/known-3"]], "46d6ca27ee07cdc6fa99c2e138cc522c")], "compute MD5, SHAx or CRC checksum of file", "\ This call computes the MD5, SHAx or CRC checksum of the @@ -1797,7 +1848,7 @@ This also forcibly removes all logical volumes in the volume group (if any)."); ("pvremove", (RErr, [String "device"]), 79, [], - [InitEmpty, Always, TestOutputList ( + [InitEmpty, Always, TestOutputListOfDevices ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["pvcreate"; "/dev/sda1"]; ["vgcreate"; "VG"; "/dev/sda1"]; @@ -1806,7 +1857,7 @@ group (if any)."); ["vgremove"; "VG"]; ["pvremove"; "/dev/sda1"]; ["lvs"]], []); - InitEmpty, Always, TestOutputList ( + InitEmpty, Always, TestOutputListOfDevices ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["pvcreate"; "/dev/sda1"]; ["vgcreate"; "VG"; "/dev/sda1"]; @@ -1815,7 +1866,7 @@ group (if any)."); ["vgremove"; "VG"]; ["pvremove"; "/dev/sda1"]; ["vgs"]], []); - InitEmpty, Always, TestOutputList ( + InitEmpty, Always, TestOutputListOfDevices ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["pvcreate"; "/dev/sda1"]; ["vgcreate"; "VG"; "/dev/sda1"]; @@ -2266,6 +2317,91 @@ even if the filesystem appears to be clean (C<-f>). This command is only needed because of C (q.v.). Normally you should use C."); + ("sleep", (RErr, [Int "secs"]), 109, [], + [InitNone, Always, TestRun ( + [["sleep"; "1"]])], + "sleep for some seconds", + "\ +Sleep for C seconds."); + + ("ntfs_3g_probe", (RInt "status", [Bool "rw"; String "device"]), 110, [], + [InitNone, Always, TestOutputInt ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["mkfs"; "ntfs"; "/dev/sda1"]; + ["ntfs_3g_probe"; "true"; "/dev/sda1"]], 0); + InitNone, Always, TestOutputInt ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["mkfs"; "ext2"; "/dev/sda1"]; + ["ntfs_3g_probe"; "true"; "/dev/sda1"]], 12)], + "probe NTFS volume", + "\ +This command runs the L command which probes +an NTFS C for mountability. (Not all NTFS volumes can +be mounted read-write, and some cannot be mounted at all). + +C is a boolean flag. Set it to true if you want to test +if the volume can be mounted read-write. Set it to false if +you want to test if the volume can be mounted read-only. + +The return value is an integer which C<0> if the operation +would succeed, or some non-zero value documented in the +L manual page."); + + ("sh", (RString "output", [String "command"]), 111, [], + [], (* XXX needs tests *) + "run a command via the shell", + "\ +This call runs a command from the guest filesystem via the +guest's C. + +This is like C, but passes the command to: + + /bin/sh -c \"command\" + +Depending on the guest's shell, this usually results in +wildcards being expanded, shell expressions being interpolated +and so on. + +All the provisos about C apply to this call."); + + ("sh_lines", (RStringList "lines", [String "command"]), 112, [], + [], (* XXX needs tests *) + "run a command via the shell returning lines", + "\ +This is the same as C, but splits the result +into a list of lines. + +See also: C"); + + ("glob_expand", (RStringList "paths", [String "pattern"]), 113, [], + [InitBasicFS, Always, TestOutputList ( + [["mkdir_p"; "/a/b/c"]; + ["touch"; "/a/b/c/d"]; + ["touch"; "/a/b/c/e"]; + ["glob_expand"; "/a/b/c/*"]], ["/a/b/c/d"; "/a/b/c/e"]); + InitBasicFS, Always, TestOutputList ( + [["mkdir_p"; "/a/b/c"]; + ["touch"; "/a/b/c/d"]; + ["touch"; "/a/b/c/e"]; + ["glob_expand"; "/a/*/c/*"]], ["/a/b/c/d"; "/a/b/c/e"]); + InitBasicFS, Always, TestOutputList ( + [["mkdir_p"; "/a/b/c"]; + ["touch"; "/a/b/c/d"]; + ["touch"; "/a/b/c/e"]; + ["glob_expand"; "/a/*/x/*"]], [])], + "expand a wildcard path", + "\ +This command searches for all the pathnames matching +C according to the wildcard expansion rules +used by the shell. + +If no paths match, then this returns an empty list +(note: not an error). + +It is just a wrapper around the C L function +with flags C. +See that manual page for more details."); + ] let all_functions = non_daemon_functions @ daemon_functions @@ -2373,6 +2509,14 @@ let statvfs_cols = [ "namemax", `Int; ] +(* Used for testing language bindings. *) +type callt = + | CallString of string + | CallOptString of string option + | CallStringList of string list + | CallInt of int + | CallBool of bool + (* Useful functions. * Note we don't want to use any external OCaml libraries which * makes this a bit harder than it should be. @@ -2490,6 +2634,7 @@ let name_of_argt = function let seq_of_test = function | TestRun s | TestOutput (s, _) | TestOutputList (s, _) + | TestOutputListOfDevices (s, _) | TestOutputInt (s, _) | TestOutputTrue s | TestOutputFalse s | TestOutputLength (s, _) | TestOutputStruct (s, _) | TestLastFail s -> s @@ -3413,8 +3558,12 @@ and generate_daemon_actions () = pr " struct guestfs_%s_args args;\n" name; List.iter ( function + (* Note we allow the string to be writable, in order to + * allow device name translation. This is safe because + * we can modify the string (passed from RPC). + *) | String n - | OptString n -> pr " const char *%s;\n" n + | OptString n -> pr " char *%s;\n" n | StringList n -> pr " char **%s;\n" n | Bool n -> pr " int %s;\n" n | Int n -> pr " int %s;\n" n @@ -3537,7 +3686,7 @@ and generate_daemon_actions () = ) daemon_functions; pr " default:\n"; - pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d\", proc_nr);\n"; + pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d, set LIBGUESTFS_PATH to point to the matching libguestfs appliance directory\", proc_nr);\n"; pr " }\n"; pr "}\n"; pr "\n"; @@ -3725,11 +3874,6 @@ and generate_tests () = static guestfs_h *g; static int suppress_error = 0; -/* This will be 's' or 'h' depending on whether the guest kernel - * names IDE devices /dev/sd* or /dev/hd*. - */ -static char devchar = 's'; - static void print_error (guestfs_h *g, void *data, const char *msg) { if (!suppress_error) @@ -3786,11 +3930,9 @@ int main (int argc, char *argv[]) { char c = 0; int failed = 0; - const char *srcdir; const char *filename; - int fd, i; + int fd; int nr_tests, test_num = 0; - char **devs; no_test_warnings (); @@ -3888,36 +4030,26 @@ int main (int argc, char *argv[]) exit (1); } + if (guestfs_add_drive_ro (g, \"../images/test.sqsh\") == -1) { + printf (\"guestfs_add_drive_ro ../images/test.sqsh FAILED\\n\"); + exit (1); + } + if (guestfs_launch (g) == -1) { printf (\"guestfs_launch FAILED\\n\"); exit (1); } + + /* Set a timeout in case qemu hangs during launch (RHBZ#505329). */ + alarm (600); + if (guestfs_wait_ready (g) == -1) { printf (\"guestfs_wait_ready FAILED\\n\"); exit (1); } - /* Detect if the appliance uses /dev/sd* or /dev/hd* in device - * names. This changed between RHEL 5 and RHEL 6 so we have to - * support both. - */ - devs = guestfs_list_devices (g); - if (devs == NULL || devs[0] == NULL) { - printf (\"guestfs_list_devices FAILED\\n\"); - exit (1); - } - if (strncmp (devs[0], \"/dev/sd\", 7) == 0) - devchar = 's'; - else if (strncmp (devs[0], \"/dev/hd\", 7) == 0) - devchar = 'h'; - else { - printf (\"guestfs_list_devices returned unexpected string '%%s'\\n\", - devs[0]); - exit (1); - } - for (i = 0; devs[i] != NULL; ++i) - free (devs[i]); - free (devs); + /* Cancel previous alarm. */ + alarm (0); nr_tests = %d; @@ -4061,9 +4193,6 @@ and generate_one_test_body name i test_name init test = | TestOutput (seq, expected) -> pr " /* TestOutput for %s (%d) */\n" name i; pr " char expected[] = \"%s\";\n" (c_quote expected); - if String.length expected > 7 && - String.sub expected 0 7 = "/dev/sd" then - pr " expected[5] = devchar;\n"; let seq, last = get_seq_last seq in let test () = pr " if (strcmp (r, expected) != 0) {\n"; @@ -4086,8 +4215,35 @@ and generate_one_test_body name i test_name init test = pr " }\n"; pr " {\n"; pr " char expected[] = \"%s\";\n" (c_quote str); - if String.length str > 7 && String.sub str 0 7 = "/dev/sd" then - pr " expected[5] = devchar;\n"; + pr " if (strcmp (r[%d], expected) != 0) {\n" i; + pr " fprintf (stderr, \"%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", expected, r[%d]);\n" test_name i; + pr " return -1;\n"; + pr " }\n"; + pr " }\n" + ) expected; + pr " if (r[%d] != NULL) {\n" (List.length expected); + pr " fprintf (stderr, \"%s: extra elements returned from command\\n\");\n" + test_name; + pr " print_strings (r);\n"; + 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 () = + iteri ( + fun i str -> + pr " if (!r[%d]) {\n" i; + pr " fprintf (stderr, \"%s: short list returned from command\\n\");\n" test_name; + pr " print_strings (r);\n"; + pr " return -1;\n"; + pr " }\n"; + pr " {\n"; + pr " char expected[] = \"%s\";\n" (c_quote str); + pr " r[%d][5] = 's';\n" i; pr " if (strcmp (r[%d], expected) != 0) {\n" i; pr " fprintf (stderr, \"%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", expected, r[%d]);\n" test_name i; pr " return -1;\n"; @@ -4233,8 +4389,6 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd = | String n, arg | OptString n, arg -> pr " char %s[] = \"%s\";\n" n (c_quote arg); - if String.length arg > 7 && String.sub arg 0 7 = "/dev/sd" then - pr " %s[5] = devchar;\n" n | Int _, _ | Bool _, _ | FileIn _, _ | FileOut _, _ -> () @@ -4243,8 +4397,6 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd = iteri ( fun i str -> pr " char %s_%d[] = \"%s\";\n" n i (c_quote str); - if String.length str > 7 && String.sub str 0 7 = "/dev/sd" then - pr " %s_%d[5] = devchar;\n" n i ) strs; pr " char *%s[] = {\n" n; iteri ( @@ -4710,6 +4862,8 @@ generator (const char *text, int state) len = strlen (text); } + rl_attempted_completion_over = 1; + while ((name = commands[index]) != NULL) { index++; if (strncasecmp (name, text, len) == 0) @@ -4726,8 +4880,12 @@ char **do_completion (const char *text, int start, int end) char **matches = NULL; #ifdef HAVE_LIBREADLINE + rl_completion_append_character = ' '; + if (start == 0) matches = rl_completion_matches (text, generator); + else if (complete_dest_paths) + matches = rl_completion_matches (text, complete_dest_paths_generator); #endif return matches; @@ -4842,8 +5000,14 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true) List.iter ( function | String n - | OptString n -> next (); pr "const char *%s" n - | StringList n -> next (); pr "char * const* const %s" n + | OptString n -> + next (); + if not in_daemon then pr "const char *%s" n + else pr "char *%s" n + | StringList n -> + next (); + if not in_daemon then pr "char * const* const %s" n + else pr "char **%s" n | Bool n -> next (); pr "int %s" n | Int n -> next (); pr "int %s" n | FileIn n @@ -5395,13 +5559,19 @@ DESTROY (g) generate_call_args ~handle:"g" (snd style); pr "\n"; pr " guestfs_h *g;\n"; - List.iter ( - function - | String n | FileIn n | FileOut n -> pr " char *%s;\n" n - | OptString n -> pr " char *%s;\n" n - | StringList n -> pr " char **%s;\n" n - | Bool n -> pr " int %s;\n" n - | Int n -> pr " int %s;\n" n + iteri ( + fun i -> + function + | String n | FileIn n | FileOut n -> pr " char *%s;\n" n + | OptString n -> + (* http://www.perlmonks.org/?node_id=554277 + * Note that the implicit handle argument means we have + * to add 1 to the ST(x) operator. + *) + pr " char *%s = SvOK(ST(%d)) ? SvPV_nolen(ST(%d)) : NULL;\n" n (i+1) (i+1) + | StringList n -> pr " char **%s;\n" n + | Bool n -> pr " int %s;\n" n + | Int n -> pr " int %s;\n" n ) (snd style); let do_cleanups () = @@ -6330,14 +6500,16 @@ static VALUE ruby_guestfs_close (VALUE gv) List.iter ( function | String n | FileIn n | FileOut n -> + pr " Check_Type (%sv, T_STRING);\n" n; pr " const char *%s = StringValueCStr (%sv);\n" n n; pr " if (!%s)\n" n; pr " rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n"; pr " \"%s\", \"%s\");\n" n name | OptString n -> - pr " const char *%s = StringValueCStr (%sv);\n" n n + pr " const char *%s = !NIL_P (%sv) ? StringValueCStr (%sv) : NULL;\n" n n n | StringList n -> - pr " char **%s;" n; + pr " char **%s;\n" n; + pr " Check_Type (%sv, T_ARRAY);\n" n; pr " {\n"; pr " int i, len;\n"; pr " len = RARRAY_LEN (%sv);\n" n; @@ -6349,7 +6521,8 @@ static VALUE ruby_guestfs_close (VALUE gv) pr " }\n"; pr " %s[len] = NULL;\n" n; pr " }\n"; - | Bool n + | Bool n -> + pr " int %s = RTEST (%sv);\n" n n | Int n -> pr " int %s = NUM2INT (%sv);\n" n n ) (snd style); @@ -6863,10 +7036,14 @@ Java_com_redhat_et_libguestfs_GuestFS__1close List.iter ( function | String n - | OptString n | FileIn n | FileOut n -> pr " %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n + | OptString n -> + (* This is completely undocumented, but Java null becomes + * a NULL parameter. + *) + pr " %s = j%s ? (*env)->GetStringUTFChars (env, j%s, NULL) : NULL;\n" n n n | StringList n -> pr " %s_len = (*env)->GetArrayLength (env, j%s);\n" n n; pr " %s = guestfs_safe_malloc (g, sizeof (char *) * (%s_len+1));\n" n n; @@ -6890,10 +7067,12 @@ Java_com_redhat_et_libguestfs_GuestFS__1close List.iter ( function | String n - | OptString n | FileIn n | FileOut n -> pr " (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n + | OptString n -> + pr " if (j%s)\n" n; + pr " (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n | StringList n -> pr " for (i = 0; i < %s_len; ++i) {\n" n; pr " jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n" @@ -7264,6 +7443,8 @@ print_strings (char * const* const argv) | Bool n -> pr " printf (\"%%s\\n\", %s ? \"true\" : \"false\");\n" n | Int n -> pr " printf (\"%%d\\n\", %s);\n" n ) (snd style); + pr " /* Java changes stdout line buffering so we need this: */\n"; + pr " fflush (stdout);\n"; pr " return 0;\n"; pr "}\n"; pr "\n" in @@ -7392,6 +7573,222 @@ print_strings (char * const* const argv) ) ) tests +and generate_ocaml_bindtests () = + generate_header OCamlStyle GPLv2; + + pr "\ +let () = + let g = Guestfs.create () in +"; + + let mkargs args = + String.concat " " ( + List.map ( + function + | CallString s -> "\"" ^ s ^ "\"" + | CallOptString None -> "None" + | CallOptString (Some s) -> sprintf "(Some \"%s\")" s + | CallStringList xs -> + "[|" ^ String.concat ";" (List.map (sprintf "\"%s\"") xs) ^ "|]" + | CallInt i when i >= 0 -> string_of_int i + | CallInt i (* when i < 0 *) -> "(" ^ string_of_int i ^ ")" + | CallBool b -> string_of_bool b + ) args + ) + in + + generate_lang_bindtests ( + fun f args -> pr " Guestfs.%s g %s;\n" f (mkargs args) + ); + + pr "print_endline \"EOF\"\n" + +and generate_perl_bindtests () = + pr "#!/usr/bin/perl -w\n"; + generate_header HashStyle GPLv2; + + pr "\ +use strict; + +use Sys::Guestfs; + +my $g = Sys::Guestfs->new (); +"; + + let mkargs args = + String.concat ", " ( + List.map ( + function + | CallString s -> "\"" ^ s ^ "\"" + | CallOptString None -> "undef" + | CallOptString (Some s) -> sprintf "\"%s\"" s + | CallStringList xs -> + "[" ^ String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "]" + | CallInt i -> string_of_int i + | CallBool b -> if b then "1" else "0" + ) args + ) + in + + generate_lang_bindtests ( + fun f args -> pr "$g->%s (%s);\n" f (mkargs args) + ); + + pr "print \"EOF\\n\"\n" + +and generate_python_bindtests () = + generate_header HashStyle GPLv2; + + pr "\ +import guestfs + +g = guestfs.GuestFS () +"; + + let mkargs args = + String.concat ", " ( + List.map ( + function + | CallString s -> "\"" ^ s ^ "\"" + | CallOptString None -> "None" + | CallOptString (Some s) -> sprintf "\"%s\"" s + | CallStringList xs -> + "[" ^ String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "]" + | CallInt i -> string_of_int i + | CallBool b -> if b then "1" else "0" + ) args + ) + in + + generate_lang_bindtests ( + fun f args -> pr "g.%s (%s)\n" f (mkargs args) + ); + + pr "print \"EOF\"\n" + +and generate_ruby_bindtests () = + generate_header HashStyle GPLv2; + + pr "\ +require 'guestfs' + +g = Guestfs::create() +"; + + let mkargs args = + String.concat ", " ( + List.map ( + function + | CallString s -> "\"" ^ s ^ "\"" + | CallOptString None -> "nil" + | CallOptString (Some s) -> sprintf "\"%s\"" s + | CallStringList xs -> + "[" ^ String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "]" + | CallInt i -> string_of_int i + | CallBool b -> string_of_bool b + ) args + ) + in + + generate_lang_bindtests ( + fun f args -> pr "g.%s(%s)\n" f (mkargs args) + ); + + pr "print \"EOF\\n\"\n" + +and generate_java_bindtests () = + generate_header CStyle GPLv2; + + pr "\ +import com.redhat.et.libguestfs.*; + +public class Bindtests { + public static void main (String[] argv) + { + try { + GuestFS g = new GuestFS (); +"; + + let mkargs args = + String.concat ", " ( + List.map ( + function + | CallString s -> "\"" ^ s ^ "\"" + | CallOptString None -> "null" + | CallOptString (Some s) -> sprintf "\"%s\"" s + | CallStringList xs -> + "new String[]{" ^ + String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "}" + | CallInt i -> string_of_int i + | CallBool b -> string_of_bool b + ) args + ) + in + + generate_lang_bindtests ( + fun f args -> pr " g.%s (%s);\n" f (mkargs args) + ); + + pr " + System.out.println (\"EOF\"); + } + catch (Exception exn) { + System.err.println (exn); + System.exit (1); + } + } +} +" + +and generate_haskell_bindtests () = + () (* XXX Haskell bindings need to be fleshed out. *) + +(* Language-independent bindings tests - we do it this way to + * ensure there is parity in testing bindings across all languages. + *) +and generate_lang_bindtests call = + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList []; CallBool false; + CallInt 0; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString None; + CallStringList []; CallBool false; + CallInt 0; CallString "123"; CallString "456"]; + call "test0" [CallString ""; CallOptString (Some "def"); + CallStringList []; CallBool false; + CallInt 0; CallString "123"; CallString "456"]; + call "test0" [CallString ""; CallOptString (Some ""); + CallStringList []; CallBool false; + CallInt 0; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool false; + CallInt 0; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"; "2"]; CallBool false; + CallInt 0; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool true; + CallInt 0; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool false; + CallInt (-1); CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool false; + CallInt (-2); CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool false; + CallInt 1; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool false; + CallInt 2; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool false; + CallInt 4095; CallString "123"; CallString "456"]; + call "test0" [CallString "abc"; CallOptString (Some "def"); + CallStringList ["1"]; CallBool false; + CallInt 0; CallString ""; CallString ""] + + (* XXX Add here tests of the return and error functions. *) + let output_to filename = let filename_new = filename ^ ".new" in chan := open_out filename_new; @@ -7489,6 +7886,10 @@ Run it from the top source directory using the command generate_ocaml_c (); close (); + let close = output_to "ocaml/bindtests.ml" in + generate_ocaml_bindtests (); + close (); + let close = output_to "perl/Guestfs.xs" in generate_perl_xs (); close (); @@ -7497,6 +7898,10 @@ Run it from the top source directory using the command generate_perl_pm (); close (); + let close = output_to "perl/bindtests.pl" in + generate_perl_bindtests (); + close (); + let close = output_to "python/guestfs-py.c" in generate_python_c (); close (); @@ -7505,10 +7910,18 @@ Run it from the top source directory using the command generate_python_py (); close (); + let close = output_to "python/bindtests.py" in + generate_python_bindtests (); + close (); + let close = output_to "ruby/ext/guestfs/_guestfs.c" in generate_ruby_c (); close (); + let close = output_to "ruby/bindtests.rb" in + generate_ruby_bindtests (); + close (); + let close = output_to "java/com/redhat/et/libguestfs/GuestFS.java" in generate_java_java (); close (); @@ -7537,6 +7950,14 @@ Run it from the top source directory using the command generate_java_c (); close (); + let close = output_to "java/Bindtests.java" in + generate_java_bindtests (); + close (); + let close = output_to "haskell/Guestfs.hs" in generate_haskell_hs (); close (); + + let close = output_to "haskell/bindtests.hs" in + generate_haskell_bindtests (); + close ();