Richard W.M. Jones
2016-Jul-15 21:37 UTC
[Libguestfs] [PATCH 0/3] mllib: Various fixes and changes to Getopt module.
The second patch is obviously not complete yet - for discussion only. Rich.
Richard W.M. Jones
2016-Jul-15 21:37 UTC
[Libguestfs] [PATCH 1/3] mllib: Fix parsing of integers on the command line and use correct int type.
Currently input such as "1ABC" or "1.1" is parsed (as 1). If there is trailing data on the command line, refuse to accept it. In addition this parses the integer into a C 'long', which is as close as we can get to the OCaml idea of a native int. This prevents the gross rounding error from the earlier code if the integer parameter was larger than 32 bits (on a 64 bit platform), but is still not completely free of rounding problems because the OCaml type is 31 or 63 bits. Probably we should have explicit 'int32' and 'int64' types in the OCaml code, instead of using (native) 'int'. --- mllib/getopt-c.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mllib/getopt-c.c b/mllib/getopt-c.c index 1f129a7..afb8793 100644 --- a/mllib/getopt-c.c +++ b/mllib/getopt-c.c @@ -247,7 +247,8 @@ guestfs_int_mllib_getopt_parse (value argsv, value specsv, value anon_funv, valu actionv = Field (specv, 1); switch (Tag_val (actionv)) { - int num; + long num; + int nchars; case 0: /* Unit of (unit -> unit) */ v = Field (actionv, 0); @@ -274,7 +275,8 @@ guestfs_int_mllib_getopt_parse (value argsv, value specsv, value anon_funv, valu break; case 5: /* Int of string * (int -> unit) */ - if (sscanf (optarg, "%d", &num) != 1) { + if (sscanf (optarg, "%ld%n", &num, &nchars) < 1 + || optarg[nchars] != '\0') { fprintf (stderr, _("'%s' is not a numeric value.\n"), guestfs_int_program_name); show_error (EXIT_FAILURE); @@ -284,7 +286,8 @@ guestfs_int_mllib_getopt_parse (value argsv, value specsv, value anon_funv, valu break; case 6: /* Set_int of string * int ref */ - if (sscanf (optarg, "%d", &num) != 1) { + if (sscanf (optarg, "%ld%n", &num, &nchars) < 1 + || optarg[nchars] != '\0') { fprintf (stderr, _("'%s' is not a numeric value.\n"), guestfs_int_program_name); show_error (EXIT_FAILURE); -- 2.7.4
Richard W.M. Jones
2016-Jul-15 21:37 UTC
[Libguestfs] [PATCH 2/3] mllib: Use L"..." and S '...' for long and short options.
--- builder/cmdline.ml | 61 +++++++++++++------------ generator/customize.ml | 29 ++++++------ mllib/common_utils.ml | 15 +++--- mllib/getopt.ml | 121 ++++++++++++++++++++++++------------------------- mllib/getopt.mli | 39 +++++++++------- 5 files changed, 135 insertions(+), 130 deletions(-) diff --git a/builder/cmdline.ml b/builder/cmdline.ml index 846c2e3..49a57ee 100644 --- a/builder/cmdline.ml +++ b/builder/cmdline.ml @@ -20,6 +20,7 @@ open Common_gettext.Gettext open Common_utils +open Getopt.OptionName open Customize_cmdline @@ -119,46 +120,46 @@ let parse_cmdline () let warn_if_partition = ref true in let argspec = [ - [ "--arch" ], Getopt.Set_string ("arch", arch), s_"Set the output architecture"; - [ "--attach" ], Getopt.String ("iso", attach_disk), s_"Attach data disk/ISO during install"; - [ "--attach-format" ], Getopt.String ("format", set_attach_format), + [ L"arch" ], Getopt.Set_string ("arch", arch), s_"Set the output architecture"; + [ L"attach" ], Getopt.String ("iso", attach_disk), s_"Attach data disk/ISO during install"; + [ L"attach-format" ], Getopt.String ("format", set_attach_format), s_"Set attach disk format"; - [ "--cache" ], Getopt.String ("dir", set_cache), s_"Set template cache dir"; - [ "--no-cache" ], Getopt.Unit no_cache, s_"Disable template cache"; - [ "--cache-all-templates" ], Getopt.Unit cache_all_mode, + [ L"cache" ], Getopt.String ("dir", set_cache), s_"Set template cache dir"; + [ L"no-cache" ], Getopt.Unit no_cache, s_"Disable template cache"; + [ L"cache-all-templates" ], Getopt.Unit cache_all_mode, s_"Download all templates to the cache"; - [ "--check-signature"; "--check-signatures" ], Getopt.Set check_signature, + [ L"check-signature"; L"check-signatures" ], Getopt.Set check_signature, s_"Check digital signatures"; - [ "--no-check-signature"; "--no-check-signatures" ], Getopt.Clear check_signature, + [ L"no-check-signature"; L"no-check-signatures" ], Getopt.Clear check_signature, s_"Disable digital signatures"; - [ "--curl" ], Getopt.Set_string ("curl", curl), s_"Set curl binary/command"; - [ "--delete-cache" ], Getopt.Unit delete_cache_mode, + [ L"curl" ], Getopt.Set_string ("curl", curl), s_"Set curl binary/command"; + [ L"delete-cache" ], Getopt.Unit delete_cache_mode, s_"Delete the template cache"; - [ "--no-delete-on-failure" ], Getopt.Clear delete_on_failure, + [ L"no-delete-on-failure" ], Getopt.Clear delete_on_failure, s_"Don't delete output file on failure"; - [ "--fingerprint" ], Getopt.String ("AAAA..", add_fingerprint), + [ L"fingerprint" ], Getopt.String ("AAAA..", add_fingerprint), s_"Fingerprint of valid signing key"; - [ "--format" ], Getopt.Set_string ("raw|qcow2", format), s_"Output format (default: raw)"; - [ "--get-kernel" ], Getopt.Unit get_kernel_mode, + [ L"format" ], Getopt.Set_string ("raw|qcow2", format), s_"Output format (default: raw)"; + [ L"get-kernel" ], Getopt.Unit get_kernel_mode, s_"Get kernel from image"; - [ "--gpg" ], Getopt.Set_string ("gpg", gpg), s_"Set GPG binary/command"; - [ "-l"; "--list" ], Getopt.Unit list_mode, s_"List available templates"; - [ "--long" ], Getopt.Unit list_set_long, s_"Shortcut for --list-format long"; - [ "--list-format" ], Getopt.String ("short|long|json", list_set_format), + [ L"gpg" ], Getopt.Set_string ("gpg", gpg), s_"Set GPG binary/command"; + [ S 'l'; L"list" ], Getopt.Unit list_mode, s_"List available templates"; + [ L"long" ], Getopt.Unit list_set_long, s_"Shortcut for --list-format long"; + [ L"list-format" ], Getopt.String ("short|long|json", list_set_format), s_"Set the format for --list (default: short)"; - [ "--machine-readable" ], Getopt.Set machine_readable, s_"Make output machine readable"; - [ "-m"; "--memsize" ], Getopt.Int ("mb", set_memsize), s_"Set memory size"; - [ "--network" ], Getopt.Set network, s_"Enable appliance network (default)"; - [ "--no-network" ], Getopt.Clear network, s_"Disable appliance network"; - [ "--notes" ], Getopt.Unit notes_mode, s_"Display installation notes"; - [ "-o"; "--output" ], Getopt.Set_string ("file", output), s_"Set output filename"; - [ "--print-cache" ], Getopt.Unit print_cache_mode, + [ L"machine-readable" ], Getopt.Set machine_readable, s_"Make output machine readable"; + [ S 'm'; L"memsize" ], Getopt.Int ("mb", set_memsize), s_"Set memory size"; + [ L"network" ], Getopt.Set network, s_"Enable appliance network (default)"; + [ L"no-network" ], Getopt.Clear network, s_"Disable appliance network"; + [ L"notes" ], Getopt.Unit notes_mode, s_"Display installation notes"; + [ S 'o'; L"output" ], Getopt.Set_string ("file", output), s_"Set output filename"; + [ L"print-cache" ], Getopt.Unit print_cache_mode, s_"Print info about template cache"; - [ "--size" ], Getopt.String ("size", set_size), s_"Set output disk size"; - [ "--smp" ], Getopt.Int ("vcpus", set_smp), s_"Set number of vCPUs"; - [ "--source" ], Getopt.String ("URL", add_source), s_"Set source URL"; - [ "--no-sync" ], Getopt.Clear sync, s_"Do not fsync output file on exit"; - [ "--no-warn-if-partition" ], Getopt.Clear warn_if_partition, + [ L"size" ], Getopt.String ("size", set_size), s_"Set output disk size"; + [ L"smp" ], Getopt.Int ("vcpus", set_smp), s_"Set number of vCPUs"; + [ L"source" ], Getopt.String ("URL", add_source), s_"Set source URL"; + [ L"no-sync" ], Getopt.Clear sync, s_"Do not fsync output file on exit"; + [ L"no-warn-if-partition" ], Getopt.Clear warn_if_partition, s_"Do not warn if writing to a partition"; ] in let customize_argspec, get_customize_ops = Customize_cmdline.argspec () in diff --git a/generator/customize.ml b/generator/customize.ml index 0924732..259cd26 100644 --- a/generator/customize.ml +++ b/generator/customize.ml @@ -590,6 +590,7 @@ open Printf open Common_utils open Common_gettext.Gettext +open Getopt.OptionName open Customize_utils @@ -652,7 +653,7 @@ let rec argspec () | { op_type = Unit; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.Unit (fun () -> push_front %s ops),\n" discrim; pr " s_\"%s\"\n" shortdesc; pr " ),\n"; @@ -660,7 +661,7 @@ let rec argspec () | { op_type = String v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (s_\"%s\", fun s -> push_front (%s s) ops),\n" v discrim; pr " s_\"%s\"\n" shortdesc; pr " ),\n"; @@ -668,7 +669,7 @@ let rec argspec () | { op_type = StringPair v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -681,7 +682,7 @@ let rec argspec () | { op_type = StringList v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -694,7 +695,7 @@ let rec argspec () | { op_type = TargetLinks v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -707,7 +708,7 @@ let rec argspec () | { op_type = PasswordSelector v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -720,7 +721,7 @@ let rec argspec () | { op_type = UserPasswordSelector v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -734,7 +735,7 @@ let rec argspec () | { op_type = SSHKeySelector v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -748,7 +749,7 @@ let rec argspec () | { op_type = StringFn (v, fn); op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -761,7 +762,7 @@ let rec argspec () | { op_type = SMPoolSelector v; op_name = name; op_discrim = discrim; op_shortdesc = shortdesc; op_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -778,7 +779,7 @@ let rec argspec () | { flag_type = FlagBool default; flag_ml_var = var; flag_name = name; flag_shortdesc = shortdesc; flag_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; if default (* is true *) then pr " Getopt.Clear %s,\n" var else @@ -790,7 +791,7 @@ let rec argspec () flag_name = name; flag_shortdesc = shortdesc; flag_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -803,7 +804,7 @@ let rec argspec () flag_name = name; flag_shortdesc = shortdesc; flag_pod_longdesc = longdesc } -> pr " (\n"; - pr " [ \"--%s\" ],\n" name; + pr " [ L\"%s\" ],\n" name; pr " Getopt.String (\n"; pr " s_\"%s\",\n" v; pr " fun s ->\n"; @@ -855,7 +856,7 @@ pr " ] in try let ((_, spec, _), _, _) = List.find ( fun ((keys, _, _), _, _) -> - List.mem (\"--\" ^ cmd) keys + List.mem (L cmd) keys ) argspec in (match spec with | Getopt.Unit fn -> fn () diff --git a/mllib/common_utils.ml b/mllib/common_utils.ml index 3bbfa46..e7ee84a 100644 --- a/mllib/common_utils.ml +++ b/mllib/common_utils.ml @@ -19,6 +19,7 @@ open Printf open Common_gettext.Gettext +open Getopt.OptionName module Char = struct include Char @@ -571,13 +572,13 @@ let create_standard_options argspec ?anon_fun usage_msg let set_debug_gc () at_exit (fun () -> Gc.compact()) in let argspec = [ - [ "-V"; "--version" ], Getopt.Unit print_version_and_exit, s_"Display version and exit"; - [ "-v"; "--verbose" ], Getopt.Unit set_verbose, s_"Enable libguestfs debugging messages"; - [ "-x" ], Getopt.Unit set_trace, s_"Enable tracing of libguestfs calls"; - [ "--debug-gc" ], Getopt.Unit set_debug_gc, Getopt.hidden_option_description; - [ "-q"; "--quiet" ], Getopt.Unit set_quiet, s_"Don't print progress messages"; - [ "--color"; "--colors"; - "--colour"; "--colours" ], Getopt.Unit set_colours, s_"Use ANSI colour sequences even if not tty"; + [ S 'V'; L"version" ], Getopt.Unit print_version_and_exit, s_"Display version and exit"; + [ S 'v'; L"verbose" ], Getopt.Unit set_verbose, s_"Enable libguestfs debugging messages"; + [ S 'x' ], Getopt.Unit set_trace, s_"Enable tracing of libguestfs calls"; + [ L"debug-gc" ], Getopt.Unit set_debug_gc, Getopt.hidden_option_description; + [ S 'q'; L"quiet" ], Getopt.Unit set_quiet, s_"Don't print progress messages"; + [ L"color"; L"colors"; + L"colour"; L"colours" ], Getopt.Unit set_colours, s_"Use ANSI colour sequences even if not tty"; ] @ argspec in Getopt.create argspec ?anon_fun usage_msg diff --git a/mllib/getopt.ml b/mllib/getopt.ml index 550baa4..3bfcd21 100644 --- a/mllib/getopt.ml +++ b/mllib/getopt.ml @@ -29,7 +29,12 @@ type spec | Int of string * (int -> unit) | Set_int of string * int ref -type keys = string list +module OptionName = struct + type option_name = S of char | L of string +end +open OptionName + +type keys = option_name list type doc = string type usage_msg = string type anon_fun = (string -> unit) @@ -49,6 +54,14 @@ external getopt_parse : string array -> (c_keys * spec * doc) array -> ?anon_fun let column_wrap = 38 +let string_of_option_name = function + | S c -> sprintf "-%c" c + | L s -> "--" ^ s + +let string_of_option_name_no_dashes = function + | S c -> String.make 1 c + | L s -> s + let show_help h () let b = Buffer.create 1024 in @@ -58,10 +71,11 @@ let show_help h () let prologue = sprintf (f_"%s\nOptions:\n") h.usage_msg in Buffer.add_string b prologue; - let specs = List.filter ( - fun (_, _, doc) -> + let specs + List.filter ( + fun (_, _, doc) -> doc <> hidden_option_description - ) h.specs in + ) h.specs in List.iter ( fun (keys, spec, doc) -> @@ -72,7 +86,7 @@ let show_help h () in add " "; - add (String.concat ", " keys); + add (String.concat ", " (List.map string_of_option_name keys)); let arg match spec with | Unit _ @@ -109,9 +123,9 @@ let display_short_options h () List.iter ( fun (args, _, _) -> List.iter ( - fun arg -> - if is_prefix arg "-" && not (is_prefix arg "--") then - printf "%s\n" arg + function + | S _ as arg -> print_endline (string_of_option_name arg) + | L _ -> () ) args ) h.specs; exit 0 @@ -119,73 +133,44 @@ let display_long_options h () List.iter ( fun (args, _, _) -> List.iter ( - fun arg -> - if is_prefix arg "--" && arg <> "--long-options" && - arg <> "--short-options" then - printf "%s\n" arg + function + | L "short-options" | L "long-options" + | S _ -> () + | L _ as arg -> print_endline (string_of_option_name arg) ) args ) h.specs; exit 0 -(* Skip any leading '-' characters when comparing command line args. *) -let skip_dashes str - let n = String.length str in - let rec loop i - if i >= n then invalid_arg "skip_dashes" - else if String.unsafe_get str i = '-' then loop (i+1) - else i - in - let i = loop 0 in - if i = 0 then str - else String.sub str i (n-i) - let compare_command_line_args a b - compare (String.lowercase (skip_dashes a)) (String.lowercase (skip_dashes b)) + let a = String.lowercase (string_of_option_name_no_dashes a) in + let b = String.lowercase (string_of_option_name_no_dashes b) in + compare a b let create specs ?anon_fun usage_msg (* Sanity check the input *) - let validate_key key - if String.length key == 0 || key == "-" || key == "--" - || key.[0] != '-' then - invalid_arg (sprintf "invalid option key: '%s'" key) + let validate_key = function + | L"" -> invalid_arg "Getopt spec: invalid empty long option" + | L"help" -> invalid_arg "Getopt spec: should not have L\"help\"" + | L"short-options" -> + invalid_arg "Getopt spec: should not have L\"short-options\"" + | L"long-options" -> + invalid_arg "Getopt spec: should not have L\"long-options\"" + | L s when s.[0] = '-' -> + invalid_arg (sprintf "Getopt spec: L%S should not begin with a dash" + s) + | L s when String.contains s '_' -> + invalid_arg (sprintf "Getopt spec: L%S should not contain '_'" + s) + | _ -> () in - List.iter ( fun (keys, spec, doc) -> if keys == [] then invalid_arg "empty keys for Getopt spec"; - List.iter validate_key keys; + List.iter validate_key keys ) specs; - let t - { - specs = []; (* Set it later, with own options, and sorted. *) - anon_fun = anon_fun; - usage_msg = usage_msg; - } in - - let specs = specs @ [ - [ "--short-options" ], Unit (display_short_options t), hidden_option_description; - [ "--long-options" ], Unit (display_long_options t), hidden_option_description; - ] in - - (* Decide whether the help option can be added, and which switches use. *) - let has_dash_help = ref false in - let has_dash_dash_help = ref false in - List.iter ( - fun (keys, _, _) -> - if not (!has_dash_help) then - has_dash_help := List.mem "-help" keys; - if not (!has_dash_dash_help) then - has_dash_dash_help := List.mem "--help" keys; - ) specs; - let help_keys = [] @ - (if !has_dash_help then [] else [ "-help" ]) @ - (if !has_dash_dash_help then [] else [ "--help" ]) in - let specs = specs @ - (if help_keys <> [] then [ help_keys, Unit (show_help t), s_"Display brief help"; ] else []) in - - (* Sort the specs, and set them in the handle. *) + (* Sort the specs. *) let specs = List.map ( fun (keys, action, doc) -> List.hd (List.sort compare_command_line_args keys), (keys, action, doc) @@ -194,14 +179,26 @@ let create specs ?anon_fun usage_msg let cmp (arg1, _) (arg2, _) = compare_command_line_args arg1 arg2 in List.sort cmp specs in let specs = List.map snd specs in - t.specs <- specs; + let t = { + specs = specs; + anon_fun = anon_fun; + usage_msg = usage_msg; + } in + let added_options = [ + [ L"short-options" ], Unit (display_short_options t), + hidden_option_description; + [ L"long-options" ], Unit (display_long_options t), + hidden_option_description; + [ L"help" ], Unit (show_help t), s_"Display brief help"; + ] in + t.specs <- added_options @ specs; t let parse_argv t argv let specs = List.map ( fun (keys, spec, doc) -> - Array.of_list keys, spec, doc + Array.of_list (List.map string_of_option_name keys), spec, doc ) t.specs in let specs = Array.of_list specs in getopt_parse argv specs ?anon_fun:t.anon_fun t.usage_msg diff --git a/mllib/getopt.mli b/mllib/getopt.mli index 2a8bada..89d96ab 100644 --- a/mllib/getopt.mli +++ b/mllib/getopt.mli @@ -18,29 +18,34 @@ type spec | Unit of (unit -> unit) - (* Simple option with no argument; call the function. *) + (** Simple option with no argument; call the function. *) | Set of bool ref - (* Simple option with no argument; set the reference to true. *) + (** Simple option with no argument; set the reference to true. *) | Clear of bool ref - (* Simple option with no argument; set the reference to false. *) + (** Simple option with no argument; set the reference to false. *) | String of string * (string -> unit) - (* Option requiring an argument; the first element in the tuple - is the documentation string of the argument, and the second - is the function to call. *) + (** Option requiring an argument; the first element in the tuple + is the documentation string of the argument, and the second + is the function to call. *) | Set_string of string * string ref - (* Option requiring an argument; the first element in the tuple - is the documentation string of the argument, and the second - is the reference to be set. *) + (** Option requiring an argument; the first element in the tuple + is the documentation string of the argument, and the second + is the reference to be set. *) | Int of string * (int -> unit) - (* Option requiring an integer value as argument; the first - element in the tuple is the documentation string of the - argument, and the second is the function to call. *) + (** Option requiring an integer value as argument; the first + element in the tuple is the documentation string of the + argument, and the second is the function to call. *) | Set_int of string * int ref - (* Option requiring an integer value as argument; the first - element in the tuple is the documentation string of the - argument, and the second is the reference to be set. *) + (** Option requiring an integer value as argument; the first + element in the tuple is the documentation string of the + argument, and the second is the reference to be set. *) -type keys = string list +module OptionName : sig + type option_name + | S of char (** short option like -a *) + | L of string (** long option like --add *) +end +type keys = OptionName.option_name list type doc = string type usage_msg = string type anon_fun = (string -> unit) @@ -49,7 +54,7 @@ type speclist = (keys * spec * doc) list val hidden_option_description : string -val compare_command_line_args : string -> string -> int +val compare_command_line_args : OptionName.option_name -> OptionName.option_name -> int (** Compare command line arguments for equality, ignoring any leading [-]s. *) type t -- 2.7.4
Richard W.M. Jones
2016-Jul-15 21:37 UTC
[Libguestfs] [PATCH 3/3] mllib: tests: Add tests of the new Getopt module.
--- .gitignore | 1 + mllib/Makefile.am | 30 ++++++++- mllib/getopt_tests.ml | 68 ++++++++++++++++++++ mllib/test-getopt.sh | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 mllib/getopt_tests.ml create mode 100755 mllib/test-getopt.sh diff --git a/.gitignore b/.gitignore index 000e984..c1ae484 100644 --- a/.gitignore +++ b/.gitignore @@ -295,6 +295,7 @@ Makefile.in /mllib/common_gettext.ml /mllib/common_utils_tests /mllib/dummy +/mllib/getopt_tests /mllib/guestfs_config.ml /mllib/JSON_tests /mllib/libdir.ml diff --git a/mllib/Makefile.am b/mllib/Makefile.am index 4f50c52..dc36f26 100644 --- a/mllib/Makefile.am +++ b/mllib/Makefile.am @@ -162,6 +162,15 @@ common_utils_tests_CPPFLAGS = \ common_utils_tests_BOBJECTS = common_utils_tests.cmo common_utils_tests_XOBJECTS = $(common_utils_tests_BOBJECTS:.cmo=.cmx) +getopt_tests_SOURCES = dummy.c +getopt_tests_CPPFLAGS = \ + -I. \ + -I$(top_builddir) \ + -I$(shell $(OCAMLC) -where) \ + -I$(top_srcdir)/src +getopt_tests_BOBJECTS = getopt_tests.cmo +getopt_tests_XOBJECTS = $(getopt_tests_BOBJECTS:.cmo=.cmx) + JSON_tests_SOURCES = dummy.c JSON_tests_BOBJECTS = JSON_tests.cmo JSON_tests_XOBJECTS = $(JSON_tests_BOBJECTS:.cmo=.cmx) @@ -171,16 +180,24 @@ if !HAVE_OCAMLOPT common_utils_tests_THEOBJECTS = $(common_utils_tests_BOBJECTS) common_utils_tests.cmo: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) +getopt_tests_THEOBJECTS = $(getopt_tests_BOBJECTS) +getopt_tests.cmo: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) + JSON_tests_THEOBJECTS = $(JSON_tests_BOBJECTS) JSON_tests.cmo: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) + BEST = c OCAMLLINKFLAGS = mlguestfs.cma -custom else common_utils_tests_THEOBJECTS = $(common_utils_tests_XOBJECTS) common_utils_tests.cmx: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) +getopt_tests_THEOBJECTS = $(getopt_tests_XOBJECTS) +getopt_tests.cmx: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) + JSON_tests_THEOBJECTS = $(JSON_tests_XOBJECTS) JSON_tests.cmx: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) + BEST = opt OCAMLLINKFLAGS = mlguestfs.cmxa endif @@ -192,6 +209,13 @@ common_utils_tests_LINK = \ $(OCAMLPACKAGES) $(OCAMLPACKAGES_TESTS) \ $(common_utils_tests_THEOBJECTS) -o $@ +getopt_tests_DEPENDENCIES = $(getopt_tests_THEOBJECTS) $(top_srcdir)/ocaml-link.sh +getopt_tests_LINK = \ + $(top_srcdir)/ocaml-link.sh -cclib '-lutils $(LIBXML2_LIBS) -lgnu' -- \ + $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLLINKFLAGS) \ + $(OCAMLPACKAGES) $(OCAMLPACKAGES_TESTS) \ + $(getopt_tests_THEOBJECTS) -o $@ + JSON_tests_DEPENDENCIES = $(JSON_tests_THEOBJECTS) $(top_srcdir)/ocaml-link.sh JSON_tests_LINK = \ $(top_srcdir)/ocaml-link.sh -- \ @@ -201,8 +225,10 @@ JSON_tests_LINK = \ TESTS_ENVIRONMENT = $(top_builddir)/run --test -TESTS -check_PROGRAMS +TESTS = \ + test-getopt.sh +check_PROGRAMS = \ + getopt_tests if HAVE_OCAML_PKG_OUNIT check_PROGRAMS += common_utils_tests JSON_tests diff --git a/mllib/getopt_tests.ml b/mllib/getopt_tests.ml new file mode 100644 index 0000000..fb089ec --- /dev/null +++ b/mllib/getopt_tests.ml @@ -0,0 +1,68 @@ +(* mllib + * Copyright (C) 2016 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +(* Test the Getopt module. The tests are controlled by the + * test-getopt.sh script. + *) + +open Printf + +open Common_utils +open Getopt.OptionName + +let adds = ref [] +let add_string = push_back adds + +let anons = ref [] +let anon_fun = push_back anons + +let ints = ref [] +let add_int = push_back ints + +let clear_flag = ref true +let set_flag = ref false +let si = ref 42 +let ss = ref "not set" + +let argspec = [ + [ S 'a'; L"add" ], Getopt.String ("string", add_string), "Add string"; + [ S 'c'; L"clear" ], Getopt.Clear clear_flag, "Clear flag"; + [ S 'i'; L"int" ], Getopt.Int ("int", add_int), "Add int"; + [ L"si"; L"set-int" ], Getopt.Set_int ("int", si), "Set int"; + [ L"ss"; L"set-string"], Getopt.Set_string ("string", ss), "Set string"; + [ S 't'; L"set" ], Getopt.Set set_flag, "Set flag"; +] + +let usage_msg = sprintf "%s: test the Getopt parser" prog + +let opthandle = create_standard_options argspec ~anon_fun usage_msg +let () + Getopt.parse opthandle; + + (* Implicit settings. *) + printf "trace = %b\n" (trace ()); + printf "verbose = %b\n" (verbose ()); + + (* Print the results. *) + printf "adds = [%s]\n" (String.concat ", " !adds); + printf "anons = [%s]\n" (String.concat ", " !anons); + printf "ints = [%s]\n" (String.concat ", " (List.map string_of_int !ints)); + printf "clear_flag = %b\n" !clear_flag; + printf "set_flag = %b\n" !set_flag; + printf "set_int = %d\n" !si; + printf "set_string = %s\n" !ss diff --git a/mllib/test-getopt.sh b/mllib/test-getopt.sh new file mode 100755 index 0000000..1226554 --- /dev/null +++ b/mllib/test-getopt.sh @@ -0,0 +1,168 @@ +#!/bin/bash - +# libguestfs +# Copyright (C) 2016 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Test the Getopt module. +# See also: getopt_tests.ml + +set -e +set -x + +t=./getopt_tests + +expect_fail () +{ + if "$@"; then + echo "$@" ": this command was expected to exit with an error" + exit 1 + fi +} + +# Program works. +$t + +# Flags added automatically by Common_utils. +$t | grep '^trace = false' +$t | grep '^verbose = false' + +$t -x | grep '^trace = true' +$t --verbose | grep '^verbose = true' + +# --help +$t --help | grep '^getopt_tests: test the Getopt parser' +$t --help | grep '^Options:' +$t --help | grep -- '-i, --int <int>' +$t --help | grep -- '-v, --verbose' +$t --help | grep -- '-x' + +# --version +$t --version | grep '^getopt_tests 1\.' + +# --short-options +$t --short-options | grep '^-a' +$t --short-options | grep '^-c' +$t --short-options | grep '^-i' +$t --short-options | grep '^-q' +$t --short-options | grep '^-t' +$t --short-options | grep '^-V' +$t --short-options | grep '^-v' +$t --short-options | grep '^-x' + +# --long-options +$t --long-options | grep '^--help' +$t --long-options | grep '^--add' +$t --long-options | grep '^--clear' +$t --long-options | grep '^--color' +$t --long-options | grep '^--colors' +$t --long-options | grep '^--colour' +$t --long-options | grep '^--colours' +$t --long-options | grep '^--debug-gc' +$t --long-options | grep '^--int' +$t --long-options | grep '^--quiet' +$t --long-options | grep '^--set' +$t --long-options | grep '^--set-int' +$t --long-options | grep '^--set-string' +$t --long-options | grep '^--si' +$t --long-options | grep '^--ss' +$t --long-options | grep '^--version' +$t --long-options | grep '^--verbose' + +# -a/--add parameter. +$t | grep '^adds = \[\]' +$t -a A | grep '^adds = \[A\]' +$t -a A -a B | grep '^adds = \[A, B\]' +$t --add A | grep '^adds = \[A\]' +$t --add A -a B | grep '^adds = \[A, B\]' +expect_fail $t -a +expect_fail $t --add + +# -c/--clear parameter. +$t | grep '^clear_flag = true' +$t -c | grep '^clear_flag = false' +$t --clear | grep '^clear_flag = false' + +# -i/--int parameter. +$t | grep '^ints = \[\]' +$t -i 1 | grep '^ints = \[1\]' +$t -i 1 -i 2 | grep '^ints = \[1, 2\]' +$t -i 1 --int 2 --int 3 | grep '^ints = \[1, 2, 3\]' +expect_fail $t --int + +# Non-integer parameters. +expect_fail $t --int --int +expect_fail $t --int "" +expect_fail $t --int ABC +expect_fail $t --int 0.3 +expect_fail $t --int 0E +expect_fail $t --int 0ABC + +# Negative and large integer parameters. +$t -i -1 | grep '^ints = \[-1\]' +$t -i -1073741824 | grep '^ints = \[-1073741824\]' +$t -i 1073741823 | grep '^ints = \[1073741823\]' +# These round incorrectly on 32 bit: +#$t -i -2147483648 | grep '^ints = \[-2147483648\]' +#$t -i 2147483647 | grep '^ints = \[2147483647\]' +if [ `getconf LONG_BIT` = 64 ]; then + $t -i -2147483648 | grep '^ints = \[-2147483648\]' + $t -i 2147483647 | grep '^ints = \[2147483647\]' + $t -i -4611686018427387904 | grep '^ints = \[-4611686018427387904\]' + $t -i 4611686018427387903 | grep '^ints = \[4611686018427387903\]' + # These round incorrectly on 64 bit: + #$t -i -9223372036854775808 | grep '^ints = \[-9223372036854775808\]' + #$t -i 9223372036854775807 | grep '^ints = \[9223372036854775807\]' +fi + +# -t/--set parameter. +$t | grep '^set_flag = false' +$t -t | grep '^set_flag = true' +$t --set | grep '^set_flag = true' + +# --si/--set-int parameter. +$t | grep '^set_int = 42' +$t --si 1 | grep '^set_int = 1' +$t --set-int 2 | grep '^set_int = 2' +expect_fail $t --si +expect_fail $t --set-int +expect_fail $t --set-int -i +expect_fail $t --set-int "" +expect_fail $t --set-int ABC +expect_fail $t --set-int 0.3 +expect_fail $t --set-int 0E +expect_fail $t --set-int 0ABC + +# --ss/--set-string parameter. +$t | grep '^set_string = not set' +$t --ss A | grep '^set_string = A' +$t --set-string B | grep '^set_string = B' +expect_fail $t --ss +expect_fail $t --set-string + +# Anonymous parameters. +$t | grep '^anons = \[\]' +$t 1 | grep '^anons = \[1\]' +$t 1 2 3 | grep '^anons = \[1, 2, 3\]' + +# Grouping single letter options. +$t -cti1 | grep '^clear_flag = false' +$t -cti1 | grep '^set_flag = true' +$t -cti1 | grep '^ints = \[1\]' +$t -i1 -i2 | grep '^ints = \[1, 2\]' + +# Short versions of long options (used by virt-v2v). +$t -si 1 | grep '^set_int = 1' +$t -ss A | grep '^set_string = A' -- 2.7.4
Pino Toscano
2016-Jul-18 08:13 UTC
Re: [Libguestfs] [PATCH 1/3] mllib: Fix parsing of integers on the command line and use correct int type.
On Friday, 15 July 2016 22:37:27 CEST Richard W.M. Jones wrote:> Currently input such as "1ABC" or "1.1" is parsed (as 1). If there is > trailing data on the command line, refuse to accept it. > > In addition this parses the integer into a C 'long', which is as close > as we can get to the OCaml idea of a native int. This prevents the > gross rounding error from the earlier code if the integer parameter > was larger than 32 bits (on a 64 bit platform), but is still not > completely free of rounding problems because the OCaml type is 31 or > 63 bits. > > Probably we should have explicit 'int32' and 'int64' types in the > OCaml code, instead of using (native) 'int'. > ---Ops, apologies, this should have been correct when sending the initial Getopt patch -- sending the fix I have locally here for few days. Thanks, -- Pino Toscano
Pino Toscano
2016-Jul-18 08:16 UTC
Re: [Libguestfs] [PATCH 3/3] mllib: tests: Add tests of the new Getopt module.
On Friday, 15 July 2016 22:37:29 CEST Richard W.M. Jones wrote:> ---Good idea, just a couple of notes.> +(* Test the Getopt module. The tests are controlled by the > + * test-getopt.sh script. > + *) > + > +open Printf > + > +open Common_utilsTheoretically Getopt is not tied to Common_utils, so it could be tested without it.> + > +t=./getopt_tests > + > +expect_fail () > +{ > + if "$@"; then > + echo "$@" ": this command was expected to exit with an error" > + exit 1 > + fi > +} > + > +# Program works. > +$tJust wondering whether GNU expect could be used here. Thanks, -- Pino Toscano
Maybe Matching Threads
- [PATCH v2 0/3] mllib: Various fixes and changes to Getopt module.
- [PATCH v3 0/3] mllib: Various fixes and changes to Getopt module.
- [PATCH v4 0/2] mllib: Various fixes and changes to Getopt module.
- [PATCH] RFC: OCaml tools: add and use a Getopt module
- [PATCH 0/2] RFC: add output selection for --machine-readable