From e549946c6f49d8c62539d840a896dcaaa3748c1a Mon Sep 17 00:00:00 2001 From: Rudi Grinberg Date: Tue, 9 Dec 2025 21:25:22 +0000 Subject: [PATCH 1/2] dev Signed-off-by: Rudi Grinberg From 80312180081a7a74768c1a2c87c09cf9c6a21116 Mon Sep 17 00:00:00 2001 From: Rudi Grinberg Date: Sun, 28 Dec 2025 17:11:50 +0000 Subject: [PATCH 2/2] feature: add bash as option for executing cram tests Signed-off-by: Rudi Grinberg --- doc/changes/added/13083.md | 2 + doc/reference/dune/cram.rst | 7 ++ src/dune_rules/cram/cram_exec.ml | 66 +++++++++++++++---- src/dune_rules/cram/cram_exec.mli | 1 + src/dune_rules/cram/cram_rules.ml | 6 ++ src/dune_rules/cram/cram_stanza.ml | 19 ++++++ src/dune_rules/cram/cram_stanza.mli | 9 +++ .../test-cases/cram/bash-shell.t | 32 +++++++++ 8 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 doc/changes/added/13083.md create mode 100644 test/blackbox-tests/test-cases/cram/bash-shell.t diff --git a/doc/changes/added/13083.md b/doc/changes/added/13083.md new file mode 100644 index 00000000000..99c2bdb0208 --- /dev/null +++ b/doc/changes/added/13083.md @@ -0,0 +1,2 @@ +- Add a `shell` field to the cram stanza. This field allows customizing the + shell to be `bash` rather than `sh` (#13083, @haochenx) diff --git a/doc/reference/dune/cram.rst b/doc/reference/dune/cram.rst index 6a1531faf16..92052c4b468 100644 --- a/doc/reference/dune/cram.rst +++ b/doc/reference/dune/cram.rst @@ -129,3 +129,10 @@ Cram in this field. The order of evaluation for these scripts is deterministic but is left undefined, so it is not recommended that these scripts have side effects. + + .. describe:: (shell ) + + .. versionadded:: 3.22 + + Determines the shell executable to use to execute the cram script. By + default, cram scripts will execute with ``sh``. diff --git a/src/dune_rules/cram/cram_exec.ml b/src/dune_rules/cram/cram_exec.ml index dc26ac1a4b2..7cff44db11c 100644 --- a/src/dune_rules/cram/cram_exec.ml +++ b/src/dune_rules/cram/cram_exec.ml @@ -432,14 +432,30 @@ let make_temp_dir ~script = temp_dir ;; -let run_cram_test env ~src ~script ~cram_stanzas ~temp_dir ~cwd ~timeout ~setup_scripts = +let run_cram_test + env + ~src + ~script + ~cram_stanzas + ~temp_dir + ~cwd + ~timeout + ~setup_scripts + (shell : Cram_stanza.Shell.t) + = let open Fiber.O in let* sh_script = create_sh_script cram_stanzas ~temp_dir ~setup_scripts in let env = make_run_env env ~temp_dir ~cwd in let open Fiber.O in let sh = let path = Env_path.path Env.initial in - match Bin.which ~path "sh" with + match + Bin.which + ~path + (match shell with + | Sh -> "sh" + | Bash -> "bash") + with | Some sh -> sh | None -> User_error.raise [ Pp.text "CRAM test aborted, \"sh\" can not be found in PATH" ] @@ -509,6 +525,7 @@ let run_produce_correction ~script ~timeout ~setup_scripts + shell lexbuf = let temp_dir = make_temp_dir ~script in @@ -516,7 +533,16 @@ let run_produce_correction let cwd = Path.parent_exn script in let env = make_run_env env ~temp_dir ~cwd in let open Fiber.O in - run_cram_test env ~src ~script ~cram_stanzas ~temp_dir ~cwd ~timeout ~setup_scripts + run_cram_test + env + ~src + ~script + ~cram_stanzas + ~temp_dir + ~cwd + ~timeout + ~setup_scripts + shell >>| compose_cram_output ;; @@ -538,6 +564,7 @@ let run_and_produce_output ~dst ~timeout ~setup_scripts + shell = let script_contents = Io.read_file ~binary:false script in let lexbuf = Lexbuf.from_string script_contents ~fname:(Path.to_string script) in @@ -548,7 +575,16 @@ let run_and_produce_output let env = make_run_env env ~temp_dir ~cwd in let open Fiber.O in let+ commands = - run_cram_test env ~src ~script ~cram_stanzas ~temp_dir ~cwd ~timeout ~setup_scripts + run_cram_test + env + ~src + ~script + ~cram_stanzas + ~temp_dir + ~cwd + ~timeout + ~setup_scripts + shell >>| List.filter_map ~f:(function | Cram_lexer.Command c -> Some c | Comment _ -> None) @@ -567,12 +603,17 @@ module Run = struct ; output : 'target ; timeout : (Loc.t * Time.Span.t) option ; setup_scripts : 'path list + ; shell : Cram_stanza.Shell.t } let name = "cram-run" - let version = 3 + let version = 4 - let bimap ({ src = _; dir; script; output; timeout; setup_scripts } as t) f g = + let bimap + ({ src = _; dir; script; output; timeout; setup_scripts; shell = _ } as t) + f + g + = { t with dir = f dir ; script = f script @@ -584,7 +625,7 @@ module Run = struct let is_useful_to ~memoize:_ = true - let encode { src = _; dir; script; output; timeout; setup_scripts } path target + let encode { src = _; dir; script; output; timeout; shell; setup_scripts } path target : Sexp.t = List @@ -595,11 +636,12 @@ module Run = struct option float (Option.map ~f:(fun (_, time) -> Time.Span.to_secs time) timeout)) |> Dune_sexp.to_sexp ; List (List.map ~f:path setup_scripts) + ; Atom (Cram_stanza.Shell.to_string shell) ] ;; let action - { src; dir; script; output; timeout; setup_scripts } + { src; dir; script; output; timeout; setup_scripts; shell } ~ectx:_ ~(eenv : Action.env) = @@ -612,14 +654,15 @@ module Run = struct ~dst:output ~timeout ~setup_scripts + shell ;; end include Action_ext.Make (Spec) end -let run ~src ~dir ~script ~output ~timeout ~setup_scripts = - Run.action { src; dir; script; output; timeout; setup_scripts } +let run ~src ~dir ~script ~output ~timeout ~setup_scripts shell = + Run.action { src; dir; script; output; timeout; setup_scripts; shell } ;; module Make_script = struct @@ -743,7 +786,8 @@ module Action = struct ~env:eenv.env ~script ~timeout:None - ~setup_scripts:[]) + ~setup_scripts:[] + Sh) ;; end diff --git a/src/dune_rules/cram/cram_exec.mli b/src/dune_rules/cram/cram_exec.mli index 4cd5457ba29..e6716d8bdbf 100644 --- a/src/dune_rules/cram/cram_exec.mli +++ b/src/dune_rules/cram/cram_exec.mli @@ -15,6 +15,7 @@ val run -> output:Path.Build.t -> timeout:(Loc.t * Time.Span.t) option -> setup_scripts:Path.t list + -> Cram_stanza.Shell.t -> Action.t (** Produces a diff if [src] needs to be updated *) diff --git a/src/dune_rules/cram/cram_rules.ml b/src/dune_rules/cram/cram_rules.ml index 326edd04255..d605436c8d9 100644 --- a/src/dune_rules/cram/cram_rules.ml +++ b/src/dune_rules/cram/cram_rules.ml @@ -14,6 +14,7 @@ module Spec = struct ; timeout : (Loc.t * Time.Span.t) option ; conflict_markers : Cram_stanza.Conflict_markers.t ; setup_scripts : Path.t list + ; shell : Cram_stanza.Shell.t } let make_empty ~test_name_alias = @@ -28,6 +29,7 @@ module Spec = struct ; timeout = None ; conflict_markers = Ignore ; setup_scripts = [] + ; shell = Sh } ;; end @@ -64,6 +66,7 @@ let test_rule ; timeout ; conflict_markers ; setup_scripts + ; shell } : Spec.t) (test : (Cram_test.t, error) result) @@ -151,6 +154,7 @@ let test_rule ~output ~timeout ~setup_scripts + shell |> Action.Full.make ~locks ~sandbox) |> Action_builder.with_file_targets ~file_targets:[ output ] |> Super_context.add_rule sctx ~dir ~loc @@ -302,6 +306,7 @@ let rules ~sctx ~dir tests = let conflict_markers = Option.value ~default:acc.conflict_markers stanza.conflict_markers in + let shell = Option.value ~default:acc.shell stanza.shell in let setup_scripts = let more_current_scripts = List.map stanza.setup_scripts ~f:(fun (_loc, script) -> @@ -328,6 +333,7 @@ let rules ~sctx ~dir tests = ; timeout ; conflict_markers ; setup_scripts + ; shell } )) in let extra_aliases = diff --git a/src/dune_rules/cram/cram_stanza.ml b/src/dune_rules/cram/cram_stanza.ml index 80d6bb7e884..b0b4ff65358 100644 --- a/src/dune_rules/cram/cram_stanza.ml +++ b/src/dune_rules/cram/cram_stanza.ml @@ -34,6 +34,21 @@ module Conflict_markers = struct let decode = enum (List.map all ~f:(fun x -> to_string x, x)) end +module Shell = struct + type t = + | Sh + | Bash + + let all = [ "sh", Sh; "bash", Bash ] + + let to_string = + let all = List.rev_map all ~f:Tuple.T2.swap in + fun x -> Option.value_exn (List.assoc all x) + ;; + + let decode = enum all +end + type t = { loc : Loc.t ; applies_to : applies_to @@ -46,6 +61,7 @@ type t = ; runtest_alias : (Loc.t * bool) option ; timeout : (Loc.t * Time.Span.t) option ; setup_scripts : (Loc.t * string) list + ; shell : Shell.t option } include Stanza.Make (struct @@ -108,6 +124,8 @@ let decode = (Dune_lang.Syntax.since Stanza.syntax (3, 21) >>> repeat (located string)) in Option.value scripts ~default:[] + and+ shell = + field_o "shell" (Dune_lang.Syntax.since Stanza.syntax (3, 22) >>> Shell.decode) in { loc ; alias @@ -120,5 +138,6 @@ let decode = ; timeout ; conflict_markers ; setup_scripts + ; shell }) ;; diff --git a/src/dune_rules/cram/cram_stanza.mli b/src/dune_rules/cram/cram_stanza.mli index b84c226cfb8..9f488a54ade 100644 --- a/src/dune_rules/cram/cram_stanza.mli +++ b/src/dune_rules/cram/cram_stanza.mli @@ -4,6 +4,14 @@ type applies_to = | Whole_subtree | Files_matching_in_this_dir of Predicate_lang.Glob.t +module Shell : sig + type t = + | Sh + | Bash + + val to_string : t -> string +end + module Conflict_markers : sig type t = | Error @@ -24,6 +32,7 @@ type t = ; runtest_alias : (Loc.t * bool) option ; timeout : (Loc.t * Time.Span.t) option ; setup_scripts : (Loc.t * string) list + ; shell : Shell.t option } val decode : t Dune_lang.Decoder.t diff --git a/test/blackbox-tests/test-cases/cram/bash-shell.t b/test/blackbox-tests/test-cases/cram/bash-shell.t new file mode 100644 index 00000000000..41df00b27d3 --- /dev/null +++ b/test/blackbox-tests/test-cases/cram/bash-shell.t @@ -0,0 +1,32 @@ +Demonstrate the shell field in the cram stanza + + $ cat > dune-project < (lang dune 3.22) + > EOF + + $ printShell() { + > dune trace cat | jq '.[] | select(.cat == "process" and (.args.categories | index("cram"))) | .args | .prog | split("/") | last' + > } + + $ cat >foo.t <<'EOF' + > $ echo foo + > EOF + + $ dune runtest foo.t + File "foo.t", line 1, characters 0-0: + Error: Files _build/default/foo.t and _build/default/foo.t.corrected differ. + [1] + $ printShell + "sh" + + $ cat > dune < (cram + > (shell bash)) + > EOF + + $ dune runtest foo.t + File "foo.t", line 1, characters 0-0: + Error: Files _build/default/foo.t and _build/default/foo.t.corrected differ. + [1] + $ printShell + "bash"