This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,24 @@
(cram
(enabled_if
;; On macos it looks like dune has a chance to miss filesystem events if they
;; occur too close together in time. Tests of `dune exec -w` run a program with a
;; visible side effect (touching a file), and wait for the side effect before
;; modifying the program to trigger a rebuild/rerun. On macos, these changes can
;; sometimes go undetected. Adding a delay (e.g. `sleep 1`) before modifying the
;; program seems to guarantee a rebuild will be triggered, but that is too
;; unreliable to depend on in a test so these tests are disabled on macos.
(<> "macosx" %{ocaml-config:system})))
; These tests are explicitly disabled due to flakiness
(cram
(applies_to exec-watch-server exec-watch-ignore-sigterm exec-signal)
(enabled_if false))
; CR Alizter: renable all tests again, work out what causes the CI to hang
(cram
(enabled_if false))
(cram
(deps wait-for-file.sh))

View file

@ -0,0 +1,2 @@
(executable
(name main))

View file

@ -0,0 +1,27 @@
$ export OUTPUT=output.txt
In normal (non-watching) mode, pwd is the folder from which dune is launched
$ cd bin
$ dune exec --root .. -- pwd > $OUTPUT
Entering directory '..'
Leaving directory '..'
$ cat $OUTPUT
$TESTCASE_ROOT/bin
$ rm -rf $OUTPUT
$ cd ..
In watch mode, pwd is also the folder from which dune is launched.
$ cd bin
$ dune exec --root .. -w -- pwd > $OUTPUT &
Entering directory '..'
Success, waiting for filesystem changes...
Leaving directory '..'
$ PID=$!
$ until test -s $OUTPUT; do sleep 0.1; done;
$ kill $PID
$ wait $PID
[130]
$ cat $OUTPUT
$TESTCASE_ROOT/bin
$ rm -rf $OUTPUT
$ cd ..

View file

@ -0,0 +1,69 @@
Here we test what happens if there is a signal for the program to terminate when we are
running with dune exec. Signals are dispatched from the operating system. It is usually
impossible for a program to recover after recieving such a signal.
For dune exec -w, we are indifferent to what the process we are running is actually doing
since it shouldn't affect dune's other functions.
$ DONE_FLAG=_build/done_flag
$ cat > dune-project <<EOF
> (lang dune 3.18)
> EOF
$ cat > dune <<EOF
> (executable
> (name foo)
> (libraries unix))
> EOF
$ cat > touch.ml <<EOF
> let touch path =
> let fd = Unix.openfile path [ Unix.O_CREAT ] 0o777 in
> Unix.close fd
> ;;
> EOF
This first program will signal itself with a KILL signal.:
$ cat > foo.ml <<EOF
> let () =
> Touch.touch "$DONE_FLAG";
> print_endline "about to be killed";
> let pid = Unix.getpid () in
> Unix.kill pid Sys.sigkill
> ;;
> EOF
$ LOG_FILE=_build/log_file
$ mkdir _build
When reaching a signal like SEGV dune exec -w will exit.
$ dune exec -w ./foo.exe 2> >(tee "$LOG_FILE" >&2) &
about to be killed
Command got signal KILL.
Had 1 error, waiting for filesystem changes...
fixed signal
Success, waiting for filesystem changes...
$ PID=$!
$ ./wait-for-file.sh $DONE_FLAG
Waiting for KILL signal...
$ tail -f "$LOG_FILE" | while read line; do
> echo "$line" | grep 'KILL' && break
> done
Command got signal KILL.
We can now start a new build by modifying the original program and removing the segfault.
This rebuilds successfully as indicated by the above output.
$ cat > foo.ml <<EOF
> let () =
> Touch.touch "$DONE_FLAG";
> print_endline "fixed signal";
> ;;
> EOF
$ ./wait-for-file.sh $DONE_FLAG
$ kill $PID
$ wait $PID
[130]

View file

@ -0,0 +1,66 @@
Testing stdin for watch mode dune exec
We use the done flag as a signal that the program has finished.
$ DONE_FLAG=_build/done_flag
$ cat > dune-project <<EOF
> (lang dune 3.18)
> EOF
$ cat > dune <<EOF
> (executable
> (name foo)
> (libraries unix))
> EOF
$ cat > touch.ml <<EOF
> let touch path =
> let fd = Unix.openfile path [ Unix.O_CREAT ] 0o777 in
> Unix.close fd
> ;;
> EOF
$ cat > foo.ml <<EOF
> let () =
> print_endline "what is your name?";
> let name = read_line () in
> print_endline ("hello " ^ name ^ "!");
> Touch.touch "$DONE_FLAG"
> ;;
> EOF
Our program takes in some input from stdin, so we need to make sure dune passes this along
correctly. To simulate giving this input later we use a pipe.
$ mkfifo input.pipe
$ dune exec -w -- ./foo.exe < input.pipe &
what is your name?
hello John Doe!
Success, waiting for filesystem changes...
(2) what is your name?
(2) hello Alice Johnson!
Success, waiting for filesystem changes...
$ PID=$!
$ cat > input.pipe <<EOF
> John Doe
> EOF
$ ./wait-for-file.sh $DONE_FLAG
We can trigger arebuild and give another input.
$ cat > foo.ml <<EOF
> let () =
> print_endline "(2) what is your name?";
> let name = read_line () in
> print_endline ("(2) hello " ^ name ^ "!");
> Touch.touch "$DONE_FLAG"
> ;;
> EOF
$ cat > input.pipe <<EOF
> Alice Johnson
> EOF
$ ./wait-for-file.sh $DONE_FLAG
$ kill $PID
$ wait $PID
[130]

View file

@ -0,0 +1,3 @@
(executable
(public_name foo)
(libraries unix))

View file

@ -0,0 +1,2 @@
(lang dune 3.6)
(package (name foo))

View file

@ -0,0 +1,54 @@
Test exec --watch with a program that terminates immediately.
File created by the program being exec'd. In between each experiment we'll wait
until the file exists so that dune has enough time to build and run the program
between each change to its code.
$ export DONE_FLAG=_build/done_flag
$ cat >foo.ml <<EOF
> let () = print_endline "foo"; Touch.touch "$DONE_FLAG"
> EOF
$ dune exec --watch ./foo.exe &
foo
Success, waiting for filesystem changes...
bar
Success, waiting for filesystem changes...
File "foo.ml", line 1, characters 23-24:
1 | let () = print_endline "baz
^
Error: String literal not terminated
Had 1 error, waiting for filesystem changes...
baz
Success, waiting for filesystem changes...
$ PID=$!
Wait for the $DONE_FLAG file to exist, then delete the file. This file is
created by the program being exec'd, so when it exists we know that it's safe to
change the code and proceed with the test.
$ ../wait-for-file.sh $DONE_FLAG
$ cat >foo.ml <<EOF
> let () = print_endline "bar"; Touch.touch "$DONE_FLAG"
> EOF
$ ../wait-for-file.sh $DONE_FLAG
$ cat >foo.ml <<EOF
> let () = print_endline "baz
> EOF
Wait until the error shows up in the log
$ until grep 'print_endline "baz' _build/log > /dev/null; do sleep 0.1; done
$ cat >foo.ml <<EOF
> let () = print_endline "baz"; Touch.touch "$DONE_FLAG"
> EOF
$ ../wait-for-file.sh $DONE_FLAG
Prevent the test from leaking the dune process.
$ kill $PID
$ wait $PID
[130]

View file

@ -0,0 +1,3 @@
let touch path =
let fd = Unix.openfile path [ Unix.O_CREAT ] 777 in
Unix.close fd

View file

@ -0,0 +1,41 @@
Here we test what happens if the program we are running with dune exec -w exits with a
non-zero exit code.
$ DONE_FLAG=_build/done_flag
$ cat > dune-project <<EOF
> (lang dune 3.18)
> EOF
$ cat > dune <<EOF
> (executable
> (name foo)
> (libraries unix))
> EOF
$ cat > foo.ml <<EOF
> let touch path =
> let fd = Unix.openfile path [ Unix.O_CREAT ] 0o777 in
> Unix.close fd
> ;;
>
> let () =
> touch "$DONE_FLAG";
> Printf.eprintf "oh no!\n";
> exit 1
> ;;
> EOF
The build will still be considered successful even if the program being run fails. We
output the exit code of the program to be clear to the user. It's not useful anyway since
we are in watch mode.
$ dune exec -w -- ./foo.exe &
oh no!
Program exited with code [1]
Success, waiting for filesystem changes...
$ PID=$!
$ ./wait-for-file.sh $DONE_FLAG
$ kill $PID
$ wait $PID
[130]

View file

@ -0,0 +1,3 @@
(executable
(public_name foo)
(libraries unix))

View file

@ -0,0 +1,2 @@
(lang dune 3.6)
(package (name foo))

View file

@ -0,0 +1,9 @@
let touch path =
let fd = Unix.openfile path [ Unix.O_CREAT ] 777 in
Unix.close fd
let () =
let _ = Unix.sigprocmask Unix.SIG_BLOCK [ Sys.sigterm ] in
print_endline "1: before";
touch "_build/done_flag";
Unix.sleep 1000

View file

@ -0,0 +1,17 @@
Test exec --watch with a program that ignores sigterm.
$ dune exec --watch ./foo.exe &
1: before
2: before
$ PID=$!
$ ../wait-for-file.sh _build/done_flag
$ sed -i -e 's/1: before/2: before/' foo.ml
$ ../wait-for-file.sh _build/done_flag
Prevent the test from leaking the dune process.
$ kill $PID
$ wait $PID

View file

@ -0,0 +1,3 @@
(executable
(name main)
(libraries unix))

View file

@ -0,0 +1,9 @@
let () =
let touch path =
let fd = Unix.openfile path [ Unix.O_CREAT ] 0o777 in
Unix.close fd
in
print_endline "foo";
match Sys.argv.(1) with
| exception _ -> ()
| path -> touch path

View file

@ -0,0 +1,38 @@
Test the behaviour of exec watch mode when the current directory is not the
project root directory.
"dune exec --watch" works fine when invoked at the root level
$ DONE_FLAG=_build/done_flag
$ dune exec --watch ./bin/main.exe $DONE_FLAG &
foo
Success, waiting for filesystem changes...
$ PID=$!
Wait for the $DONE_FLAG file to exist, then delete the file. This file is
created by the program being exec'd, so when it exists we know that it's safe to
change the code and proceed with the test.
$ ../wait-for-file.sh $DONE_FLAG
$ kill $PID
$ wait $PID
[130]
Perform the same test above but first enter the "bin" directory.
$ dune clean
$ cd bin
$ dune exec --root .. --watch ./bin/main.exe ../$DONE_FLAG &
Entering directory '..'
foo
Success, waiting for filesystem changes...
Leaving directory '..'
$ PID=$!
$ cd ..
$ ../wait-for-file.sh $DONE_FLAG
$ kill $PID
$ wait $PID
[130]
Test that the behaviour is the same when not running with "--watch"
$ cd bin && dune exec --root .. ./bin/main.exe
Entering directory '..'
Leaving directory '..'
foo

View file

@ -0,0 +1,19 @@
Trying to run exec in passive watch mode produces and error.
$ cat >dune-project <<EOF
> (lang dune 3.6)
> (package (name foo))
> EOF
$ cat >dune <<EOF
> (executable
> (public_name foo))
> EOF
$ cat >foo.ml <<EOF
> let () = print_endline "foo"
> EOF
$ dune exec --passive-watch-mode ./foo.exe
Error: passive watch mode is unsupported by exec
[1]

View file

@ -0,0 +1,3 @@
(executable
(public_name foo)
(libraries unix))

View file

@ -0,0 +1,2 @@
(lang dune 3.6)
(package (name foo))

View file

@ -0,0 +1,53 @@
Test exec --watch with a program that doesn't terminate immediately.
File created by the program being exec'd. In between each experiment we'll wait
until the file exists so that dune has enough time to build and run the program
between each change to its code.
$ export DONE_FLAG=_build/done_flag
$ cat >foo.ml <<EOF
> let () =
> print_endline "0: before";
> Touch.touch "$DONE_FLAG";
> Unix.sleep 1000;
> print_endline "0: after"
> EOF
$ dune exec --watch ./foo.exe &
0: before
1: before
1: after
Success, waiting for filesystem changes...
2: before
$ PID=$!
$ ../wait-for-file.sh $DONE_FLAG
Change the program so that it terminates immediately.
$ cat >foo.ml <<EOF
> let () =
> print_endline "1: before";
> Unix.sleep 0;
> print_endline "1: after";
> Touch.touch "$DONE_FLAG"
> EOF
$ ../wait-for-file.sh $DONE_FLAG
Change the program so that it no longer terminates immediately.
$ cat >foo.ml <<EOF
> let () =
> print_endline "2: before";
> Touch.touch "$DONE_FLAG";
> Unix.sleep 1000;
> print_endline "2: after"
> EOF
$ ../wait-for-file.sh $DONE_FLAG
Prevent the test from leaking the dune process.
$ kill $PID
Checking for the child process
$ pgrep -c "foo.exe" || true
0

View file

@ -0,0 +1,3 @@
let touch path =
let fd = Unix.openfile path [ Unix.O_CREAT ] 777 in
Unix.close fd

View file

@ -0,0 +1,12 @@
#!/bin/sh
#
# Wait until a file exists, then delete the file and exit
#
set -u
FILE=$1
until test -e $FILE
do
sleep 0.01
done
rm $FILE