init
This commit is contained in:
commit
d16ece28e2
22 changed files with 4798 additions and 0 deletions
3
test/dune
Normal file
3
test/dune
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(test
|
||||
(name test_inshellah)
|
||||
(libraries inshellah str))
|
||||
492
test/test_inshellah.ml
Normal file
492
test/test_inshellah.ml
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
open Inshellah.Parser
|
||||
open Inshellah.Manpage
|
||||
open Inshellah.Nushell
|
||||
|
||||
let failures = ref 0
|
||||
let passes = ref 0
|
||||
|
||||
let check name condition =
|
||||
if condition then begin
|
||||
incr passes;
|
||||
Printf.printf " PASS: %s\n" name
|
||||
end else begin
|
||||
incr failures;
|
||||
Printf.printf " FAIL: %s\n" name
|
||||
end
|
||||
|
||||
let parse txt =
|
||||
match parse_help txt with
|
||||
| Ok r -> r
|
||||
| Error msg -> failwith (Printf.sprintf "parse_help failed: %s" msg)
|
||||
|
||||
(* --- Help parser tests --- *)
|
||||
|
||||
let test_gnu_basic () =
|
||||
Printf.printf "\n== GNU basic flags ==\n";
|
||||
let r = parse " -a, --all do not ignore entries starting with .\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "both switch" (e.switch = Both ('a', "all"));
|
||||
check "no param" (e.param = None);
|
||||
check "desc" (String.length e.desc > 0)
|
||||
|
||||
let test_gnu_eq_param () =
|
||||
Printf.printf "\n== GNU = param ==\n";
|
||||
let r = parse " --block-size=SIZE scale sizes by SIZE\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "long switch" (e.switch = Long "block-size");
|
||||
check "mandatory param" (e.param = Some (Mandatory "SIZE"))
|
||||
|
||||
let test_gnu_opt_param () =
|
||||
Printf.printf "\n== GNU optional param ==\n";
|
||||
let r = parse " --color[=WHEN] color the output WHEN\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "long switch" (e.switch = Long "color");
|
||||
check "optional param" (e.param = Some (Optional "WHEN"))
|
||||
|
||||
let test_underscore_param () =
|
||||
Printf.printf "\n== Underscore in param (TIME_STYLE) ==\n";
|
||||
let r = parse " --time-style=TIME_STYLE time/date format\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "param with underscore" (e.param = Some (Mandatory "TIME_STYLE"))
|
||||
|
||||
let test_short_only () =
|
||||
Printf.printf "\n== Short-only flag ==\n";
|
||||
let r = parse " -v verbose output\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
check "short switch" ((List.hd r.entries).switch = Short 'v')
|
||||
|
||||
let test_long_only () =
|
||||
Printf.printf "\n== Long-only flag ==\n";
|
||||
let r = parse " --help display help\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
check "long switch" ((List.hd r.entries).switch = Long "help")
|
||||
|
||||
let test_multiline_desc () =
|
||||
Printf.printf "\n== Multi-line description ==\n";
|
||||
let r = parse {| --block-size=SIZE with -l, scale sizes by SIZE when printing them;
|
||||
e.g., '--block-size=M'; see SIZE format below
|
||||
|} in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "desc includes continuation" (String.length e.desc > 50)
|
||||
|
||||
let test_multiple_entries () =
|
||||
Printf.printf "\n== Multiple entries ==\n";
|
||||
let r = parse {| -a, --all do not ignore entries starting with .
|
||||
-A, --almost-all do not list implied . and ..
|
||||
--author with -l, print the author of each file
|
||||
|} in
|
||||
check "three entries" (List.length r.entries = 3)
|
||||
|
||||
let test_clap_short_sections () =
|
||||
Printf.printf "\n== Clap short with section headers ==\n";
|
||||
let r = parse {|INPUT OPTIONS:
|
||||
-e, --regexp=PATTERN A pattern to search for.
|
||||
-f, --file=PATTERNFILE Search for patterns from the given file.
|
||||
SEARCH OPTIONS:
|
||||
-s, --case-sensitive Search case sensitively.
|
||||
|} in
|
||||
check "three entries" (List.length r.entries = 3);
|
||||
let e = List.hd r.entries in
|
||||
check "first is regexp" (e.switch = Both ('e', "regexp"));
|
||||
check "first has param" (e.param = Some (Mandatory "PATTERN"))
|
||||
|
||||
let test_clap_long_style () =
|
||||
Printf.printf "\n== Clap long style (desc below flag) ==\n";
|
||||
let r = parse {| -H, --hidden
|
||||
Include hidden directories and files.
|
||||
|
||||
--no-ignore
|
||||
Do not respect ignore files.
|
||||
|} in
|
||||
check "two entries" (List.length r.entries = 2);
|
||||
let e = List.hd r.entries in
|
||||
check "hidden switch" (e.switch = Both ('H', "hidden"));
|
||||
check "desc below" (String.length e.desc > 0)
|
||||
|
||||
let test_clap_long_angle_param () =
|
||||
Printf.printf "\n== Clap long angle bracket param ==\n";
|
||||
let r = parse {| --nonprintable-notation <notation>
|
||||
Set notation for non-printable characters.
|
||||
|} in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "long switch" (e.switch = Long "nonprintable-notation");
|
||||
check "angle param" (e.param = Some (Mandatory "notation"))
|
||||
|
||||
let test_space_upper_param () =
|
||||
Printf.printf "\n== Space-separated ALL_CAPS param ==\n";
|
||||
let r = parse " -f, --foo FOO foo help\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "switch" (e.switch = Both ('f', "foo"));
|
||||
check "space param" (e.param = Some (Mandatory "FOO"))
|
||||
|
||||
let test_go_cobra_flags () =
|
||||
Printf.printf "\n== Go/Cobra flags ==\n";
|
||||
let r = parse {|Flags:
|
||||
-D, --debug Enable debug mode
|
||||
-H, --host string Daemon socket to connect to
|
||||
-v, --version Print version information
|
||||
|} in
|
||||
check "three flag entries" (List.length r.entries = 3);
|
||||
(* Check the host flag has a type param *)
|
||||
let host = List.nth r.entries 1 in
|
||||
check "host switch" (host.switch = Both ('H', "host"));
|
||||
check "host type param" (host.param = Some (Mandatory "string"))
|
||||
|
||||
let test_go_cobra_subcommands () =
|
||||
Printf.printf "\n== Go/Cobra subcommands ==\n";
|
||||
let r = parse {|Common Commands:
|
||||
run Create and run a new container from an image
|
||||
exec Execute a command in a running container
|
||||
build Build an image from a Dockerfile
|
||||
|} in
|
||||
check "has subcommands" (List.length r.subcommands > 0)
|
||||
|
||||
let test_busybox_tab () =
|
||||
Printf.printf "\n== Busybox tab-indented ==\n";
|
||||
let r = parse "\t-1\tOne column output\n\t-a\tInclude names starting with .\n" in
|
||||
check "two entries" (List.length r.entries = 2);
|
||||
check "first is -1" ((List.hd r.entries).switch = Short '1')
|
||||
|
||||
let test_no_debug_prints () =
|
||||
Printf.printf "\n== No debug side effects ==\n";
|
||||
(* The old parser had print_endline at module load time.
|
||||
If we got here without "opt param is running" on stdout, we're good. *)
|
||||
check "no debug prints" true
|
||||
|
||||
(* --- Manpage parser tests --- *)
|
||||
|
||||
let test_manpage_tp_style () =
|
||||
Printf.printf "\n== Manpage .TP style ==\n";
|
||||
let groff = {|.SH OPTIONS
|
||||
.TP
|
||||
\fB\-a\fR, \fB\-\-all\fR
|
||||
do not ignore entries starting with .
|
||||
.TP
|
||||
\fB\-A\fR, \fB\-\-almost\-all\fR
|
||||
do not list implied . and ..
|
||||
.TP
|
||||
\fB\-\-block\-size\fR=\fISIZE\fR
|
||||
with \fB\-l\fR, scale sizes by SIZE
|
||||
.SH AUTHOR
|
||||
Written by someone.
|
||||
|} in
|
||||
let result = parse_manpage_string groff in
|
||||
check "three entries" (List.length result.entries = 3);
|
||||
if List.length result.entries >= 1 then begin
|
||||
let e = List.hd result.entries in
|
||||
check "first is -a/--all" (e.switch = Both ('a', "all"));
|
||||
check "first desc" (String.length e.desc > 0)
|
||||
end;
|
||||
if List.length result.entries >= 3 then begin
|
||||
let e = List.nth result.entries 2 in
|
||||
check "block-size switch" (e.switch = Long "block-size");
|
||||
check "block-size param" (e.param = Some (Mandatory "SIZE"))
|
||||
end
|
||||
|
||||
let test_manpage_ip_style () =
|
||||
Printf.printf "\n== Manpage .IP style ==\n";
|
||||
let groff = {|.SH OPTIONS
|
||||
.IP "\fB\-k\fR, \fB\-\-insecure\fR"
|
||||
Allow insecure connections.
|
||||
.IP "\fB\-o\fR, \fB\-\-output\fR \fIfile\fR"
|
||||
Write output to file.
|
||||
.SH SEE ALSO
|
||||
|} in
|
||||
let result = parse_manpage_string groff in
|
||||
check "two entries" (List.length result.entries = 2);
|
||||
if List.length result.entries >= 1 then begin
|
||||
let e = List.hd result.entries in
|
||||
check "first is -k/--insecure" (e.switch = Both ('k', "insecure"))
|
||||
end
|
||||
|
||||
let test_manpage_groff_stripping () =
|
||||
Printf.printf "\n== Groff escape stripping ==\n";
|
||||
let s = strip_groff_escapes {|\fB\-\-color\fR[=\fIWHEN\fR]|} in
|
||||
check "font escapes removed" (not (String.contains s 'f' && String.contains s 'B'));
|
||||
check "dashes converted" (String.contains s '-');
|
||||
let s2 = strip_groff_escapes {|\(aqhello\(aq|} in
|
||||
check "aq -> quote" (String.contains s2 '\'')
|
||||
|
||||
let test_manpage_empty_options () =
|
||||
Printf.printf "\n== Manpage with no OPTIONS section ==\n";
|
||||
let groff = {|.SH NAME
|
||||
foo \- does stuff
|
||||
.SH DESCRIPTION
|
||||
Does stuff.
|
||||
|} in
|
||||
let result = parse_manpage_string groff in
|
||||
check "no entries" (List.length result.entries = 0)
|
||||
|
||||
let test_slash_switch_separator () =
|
||||
Printf.printf "\n== Slash switch separator (--long / -s) ==\n";
|
||||
let r = parse " --verbose / -v Increase verbosity\n" in
|
||||
check "one entry" (List.length r.entries = 1);
|
||||
let e = List.hd r.entries in
|
||||
check "both switch" (e.switch = Both ('v', "verbose"));
|
||||
check "no param" (e.param = None);
|
||||
check "desc" (e.desc = "Increase verbosity")
|
||||
|
||||
let test_manpage_nix3_style () =
|
||||
Printf.printf "\n== Manpage nix3 style ==\n";
|
||||
let groff = {|.SH Options
|
||||
.SS Logging-related options
|
||||
.IP "\(bu" 3
|
||||
.UR #opt-verbose
|
||||
\f(CR--verbose\fR
|
||||
.UE
|
||||
/ \f(CR-v\fR
|
||||
.IP
|
||||
Increase the logging verbosity level.
|
||||
.IP "\(bu" 3
|
||||
.UR #opt-quiet
|
||||
\f(CR--quiet\fR
|
||||
.UE
|
||||
.IP
|
||||
Decrease the logging verbosity level.
|
||||
.SH SEE ALSO
|
||||
|} in
|
||||
let result = parse_manpage_string groff in
|
||||
check "two entries" (List.length result.entries = 2);
|
||||
if List.length result.entries >= 1 then begin
|
||||
let e = List.hd result.entries in
|
||||
check "verbose is Both" (e.switch = Both ('v', "verbose"));
|
||||
check "verbose desc" (String.length e.desc > 0)
|
||||
end;
|
||||
if List.length result.entries >= 2 then begin
|
||||
let e = List.nth result.entries 1 in
|
||||
check "quiet is Long" (e.switch = Long "quiet");
|
||||
check "quiet desc" (String.length e.desc > 0)
|
||||
end
|
||||
|
||||
let test_manpage_nix3_with_params () =
|
||||
Printf.printf "\n== Manpage nix3 with params ==\n";
|
||||
let groff = {|.SH Options
|
||||
.IP "\(bu" 3
|
||||
.UR #opt-arg
|
||||
\f(CR--arg\fR
|
||||
.UE
|
||||
\fIname\fR \fIexpr\fR
|
||||
.IP
|
||||
Pass the value as the argument name to Nix functions.
|
||||
.IP "\(bu" 3
|
||||
.UR #opt-include
|
||||
\f(CR--include\fR
|
||||
.UE
|
||||
/ \f(CR-I\fR \fIpath\fR
|
||||
.IP
|
||||
Add path to search path entries.
|
||||
.IP
|
||||
This option may be given multiple times.
|
||||
.SH SEE ALSO
|
||||
|} in
|
||||
let result = parse_manpage_string groff in
|
||||
check "two entries" (List.length result.entries = 2);
|
||||
if List.length result.entries >= 1 then begin
|
||||
let e = List.hd result.entries in
|
||||
check "arg is Long" (e.switch = Long "arg");
|
||||
check "arg has param" (e.param <> None)
|
||||
end;
|
||||
if List.length result.entries >= 2 then begin
|
||||
let e = List.nth result.entries 1 in
|
||||
check "include is Both" (e.switch = Both ('I', "include"));
|
||||
check "include has path param" (e.param = Some (Mandatory "path"))
|
||||
end
|
||||
|
||||
let test_synopsis_subcommand () =
|
||||
Printf.printf "\n== SYNOPSIS subcommand detection ==\n";
|
||||
let groff = {|.SH "SYNOPSIS"
|
||||
.sp
|
||||
.nf
|
||||
\fBgit\fR \fBcommit\fR [\fB\-a\fR | \fB\-\-interactive\fR]
|
||||
.fi
|
||||
.SH "DESCRIPTION"
|
||||
|} in
|
||||
let cmd = extract_synopsis_command groff in
|
||||
check "detected git commit" (cmd = Some "git commit")
|
||||
|
||||
let test_synopsis_standalone () =
|
||||
Printf.printf "\n== SYNOPSIS standalone command ==\n";
|
||||
let groff = {|.SH Synopsis
|
||||
.LP
|
||||
\f(CRnix-build\fR [\fIpaths\fR]
|
||||
.SH Description
|
||||
|} in
|
||||
let cmd = extract_synopsis_command groff in
|
||||
check "detected nix-build" (cmd = Some "nix-build")
|
||||
|
||||
let test_synopsis_nix3 () =
|
||||
Printf.printf "\n== SYNOPSIS nix3 subcommand ==\n";
|
||||
let groff = {|.SH Synopsis
|
||||
.LP
|
||||
\f(CRnix run\fR [\fIoption\fR] \fIinstallable\fR
|
||||
.SH Description
|
||||
|} in
|
||||
let cmd = extract_synopsis_command groff in
|
||||
check "detected nix run" (cmd = Some "nix run")
|
||||
|
||||
(* --- Nushell generation tests --- *)
|
||||
|
||||
let contains s sub =
|
||||
try
|
||||
let _ = Str.search_forward (Str.regexp_string sub) s 0 in true
|
||||
with Not_found -> false
|
||||
|
||||
let test_nushell_basic () =
|
||||
Printf.printf "\n== Nushell basic extern ==\n";
|
||||
let r = parse " -a, --all do not ignore entries starting with .\n" in
|
||||
let nu = generate_extern "ls" r in
|
||||
check "has extern" (contains nu "export extern \"ls\"");
|
||||
check "has --all(-a)" (contains nu "--all(-a)");
|
||||
check "has comment" (contains nu "# do not ignore")
|
||||
|
||||
let test_nushell_param_types () =
|
||||
Printf.printf "\n== Nushell param type mapping ==\n";
|
||||
let r = parse {| -w, --width=COLS set output width
|
||||
--block-size=SIZE scale sizes
|
||||
-o, --output FILE output file
|
||||
|} in
|
||||
let nu = generate_extern "ls" r in
|
||||
check "COLS -> int" (contains nu "--width(-w): int");
|
||||
check "SIZE -> string" (contains nu "--block-size: string");
|
||||
check "FILE -> path" (contains nu "--output(-o): path")
|
||||
|
||||
let test_nushell_subcommands () =
|
||||
Printf.printf "\n== Nushell subcommands ==\n";
|
||||
let r = parse {|Common Commands:
|
||||
run Create and run a new container
|
||||
exec Execute a command
|
||||
|
||||
Flags:
|
||||
-D, --debug Enable debug mode
|
||||
|} in
|
||||
let nu = generate_extern "docker" r in
|
||||
check "has main extern" (contains nu "export extern \"docker\"");
|
||||
check "has --debug" (contains nu "--debug(-D)");
|
||||
check "has run subcommand" (contains nu "export extern \"docker run\"");
|
||||
check "has exec subcommand" (contains nu "export extern \"docker exec\"")
|
||||
|
||||
let test_nushell_from_manpage () =
|
||||
Printf.printf "\n== Nushell from manpage ==\n";
|
||||
let groff = {|.SH OPTIONS
|
||||
.TP
|
||||
\fB\-a\fR, \fB\-\-all\fR
|
||||
do not ignore entries starting with .
|
||||
.TP
|
||||
\fB\-\-block\-size\fR=\fISIZE\fR
|
||||
scale sizes by SIZE
|
||||
.SH AUTHOR
|
||||
|} in
|
||||
let result = parse_manpage_string groff in
|
||||
let nu = generate_extern "ls" result in
|
||||
check "has extern" (contains nu "export extern \"ls\"");
|
||||
check "has --all(-a)" (contains nu "--all(-a)");
|
||||
check "has --block-size" (contains nu "--block-size: string")
|
||||
|
||||
let test_nushell_module () =
|
||||
Printf.printf "\n== Nushell module wrapper ==\n";
|
||||
let r = parse " -v, --verbose verbose output\n" in
|
||||
let nu = generate_module "myapp" r in
|
||||
check "has module" (contains nu "module myapp-completions");
|
||||
check "has extern inside" (contains nu "export extern \"myapp\"");
|
||||
check "has flag" (contains nu "--verbose(-v)")
|
||||
|
||||
let test_dedup_entries () =
|
||||
Printf.printf "\n== Deduplication ==\n";
|
||||
let r = parse {| -v, --verbose verbose output
|
||||
--verbose verbose mode
|
||||
-v be verbose
|
||||
|} in
|
||||
let nu = generate_extern "test" r in
|
||||
(* Count occurrences of --verbose *)
|
||||
let count =
|
||||
let re = Str.regexp_string "--verbose" in
|
||||
let n = ref 0 in
|
||||
let i = ref 0 in
|
||||
(try while true do
|
||||
let _ = Str.search_forward re nu !i in
|
||||
incr n; i := Str.match_end ()
|
||||
done with Not_found -> ());
|
||||
!n
|
||||
in
|
||||
check "verbose appears once" (count = 1);
|
||||
check "best version kept (Both)" (contains nu "--verbose(-v)")
|
||||
|
||||
let test_dedup_manpage () =
|
||||
Printf.printf "\n== Dedup from manpage ==\n";
|
||||
let groff = {|.SH OPTIONS
|
||||
.TP
|
||||
\fB\-v\fR, \fB\-\-verbose\fR
|
||||
Be verbose.
|
||||
.SH DESCRIPTION
|
||||
Use \fB\-v\fR for verbose output.
|
||||
Use \fB\-\-verbose\fR to see more.
|
||||
|} in
|
||||
let result = parse_manpage_string groff in
|
||||
let nu = generate_extern "test" result in
|
||||
check "has --verbose(-v)" (contains nu "--verbose(-v)");
|
||||
(* Should not have standalone -v or duplicate --verbose *)
|
||||
let lines = String.split_on_char '\n' nu in
|
||||
let verbose_lines = List.filter (fun l -> contains l "verbose") lines in
|
||||
check "only one verbose line" (List.length verbose_lines = 1)
|
||||
|
||||
let test_font_boundary_spacing () =
|
||||
Printf.printf "\n== Font boundary spacing ==\n";
|
||||
(* \fB--max-results\fR\fIcount\fR should become "--max-results count" *)
|
||||
let s = strip_groff_escapes {|\fB\-\-max\-results\fR\fIcount\fR|} in
|
||||
check "has space before param" (contains s "--max-results count");
|
||||
(* \fB--color\fR[=\fIWHEN\fR] should NOT insert space before = *)
|
||||
let s2 = strip_groff_escapes {|\fB\-\-color\fR[=\fIWHEN\fR]|} in
|
||||
check "no space before =" (contains s2 "--color[=WHEN]")
|
||||
|
||||
let () =
|
||||
Printf.printf "Running help parser tests...\n";
|
||||
test_gnu_basic ();
|
||||
test_gnu_eq_param ();
|
||||
test_gnu_opt_param ();
|
||||
test_underscore_param ();
|
||||
test_short_only ();
|
||||
test_long_only ();
|
||||
test_multiline_desc ();
|
||||
test_multiple_entries ();
|
||||
test_clap_short_sections ();
|
||||
test_clap_long_style ();
|
||||
test_clap_long_angle_param ();
|
||||
test_space_upper_param ();
|
||||
test_go_cobra_flags ();
|
||||
test_go_cobra_subcommands ();
|
||||
test_busybox_tab ();
|
||||
test_no_debug_prints ();
|
||||
|
||||
Printf.printf "\nRunning manpage parser tests...\n";
|
||||
test_manpage_tp_style ();
|
||||
test_manpage_ip_style ();
|
||||
test_manpage_groff_stripping ();
|
||||
test_manpage_empty_options ();
|
||||
test_slash_switch_separator ();
|
||||
test_manpage_nix3_style ();
|
||||
test_manpage_nix3_with_params ();
|
||||
test_synopsis_subcommand ();
|
||||
test_synopsis_standalone ();
|
||||
test_synopsis_nix3 ();
|
||||
|
||||
Printf.printf "\nRunning nushell generation tests...\n";
|
||||
test_nushell_basic ();
|
||||
test_nushell_param_types ();
|
||||
test_nushell_subcommands ();
|
||||
test_nushell_from_manpage ();
|
||||
test_nushell_module ();
|
||||
|
||||
Printf.printf "\nRunning dedup and font tests...\n";
|
||||
test_dedup_entries ();
|
||||
test_dedup_manpage ();
|
||||
test_font_boundary_spacing ();
|
||||
|
||||
Printf.printf "\n=== Results: %d passed, %d failed ===\n" !passes !failures;
|
||||
if !failures > 0 then exit 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue