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,193 @@
open Stdune
open Dune_rules
module Re = Dune_re
let () =
Path.set_root (Path.External.cwd ());
Path.Build.set_build_dir (Path.Outside_build_dir.of_string "_build")
;;
let fail fmt =
Printf.ksprintf
(fun msg ->
prerr_endline msg;
exit 1)
fmt
;;
(* {1 encoding/decoding tests} Test that encoding and decoding round trip *)
let () =
for n = 0 to 3 do
let test ?min_len subst =
let subst' =
subst |> Artifact_substitution.encode ?min_len |> Artifact_substitution.decode
in
if subst' <> Some subst
then
fail
"encode and decode don't round trip!\n\
subst: %s\n\
subst |> encode: %S\n\
subst |> encode |> decode: %s"
(Dyn.to_string (Artifact_substitution.to_dyn subst))
(Artifact_substitution.encode subst ?min_len)
(match subst' with
| None -> "-"
| Some x -> Dyn.to_string (Artifact_substitution.to_dyn x))
in
let test s =
let value = Artifact_substitution.Repeat (n, s) in
test value;
let len = String.length (Artifact_substitution.encode value) in
for i = -2 to 2 do
test value ~min_len:(len + i)
done
in
test "";
test "x";
test "xyz";
test (String.make 100 'x')
done
;;
(* {1 copy tests} *)
(* {2 Test harness}
The test harness implements a slower but much simpler version of the
substitution algorithm and compare the result for various inputs between the
simpler implementation and the real one. *)
let simple_subst =
let re =
Re.compile
(Re.seq [ Re.str "%%DUNE_PLACEHOLDER:"; Re.group (Re.rep1 Re.digit); Re.char ':' ])
in
fun s ->
let slen = String.length s in
let extract_placeholder pos =
let open Option.O in
(* Look at the beginning manually otherwise it's too slow *)
if pos + 3 >= slen || s.[pos] <> '%' || s.[pos + 1] <> '%' || s.[pos + 2] <> 'D'
then None
else
let* groups = Re.exec_opt re s ~pos in
let* len = Int.of_string (Re.Group.get groups 1) in
if pos + len > slen
then None
else
let* p = Artifact_substitution.decode (String.sub s ~pos ~len) in
Some (len, p)
in
let buf = Buffer.create slen in
let rec loop pos =
if pos = slen
then Buffer.contents buf
else (
match extract_placeholder pos with
| None ->
Buffer.add_char buf s.[pos];
loop (pos + 1)
| Some (len, subst) ->
Buffer.add_string
buf
(Artifact_substitution.encode_replacement
~len
~repl:
(match subst with
| Repeat (n, s) ->
Array.make n s |> Array.to_list |> String.concat ~sep:""
| _ -> failwith "substitution value not supported"));
loop (pos + len))
in
loop 0
;;
(* Replace long sequences of the same character by the character followed by a
number between "\\{" and "}" *)
let compress_string s =
let buf = Buffer.create (String.length s * 2) in
let chain_length = ref 0 in
let last_char = ref '\000' in
let commit_chain () =
let s = Char.escaped !last_char in
if !chain_length > 5
then Printf.bprintf buf "%s\\{%d}" s !chain_length
else
for _i = 1 to !chain_length do
Buffer.add_string buf s
done
in
for i = 0 to String.length s - 1 do
let c = s.[i] in
if c = !last_char
then incr chain_length
else (
commit_chain ();
last_char := c;
chain_length := 1)
done;
commit_chain ();
Buffer.contents buf
;;
let test input =
let expected = simple_subst input in
let buf = Buffer.create (String.length expected) in
Fiber.run
~iter:(fun () -> assert false)
(let ofs = ref 0 in
let open Fiber.O in
let input buf pos len =
let to_copy = min len (String.length input - !ofs) in
Bytes.blit_string ~src:input ~dst:buf ~src_pos:!ofs ~dst_pos:pos ~len:to_copy;
ofs := !ofs + to_copy;
to_copy
in
let output = Buffer.add_subbytes buf in
let+ (_ : Artifact_substitution.status) =
Artifact_substitution.copy
~conf:Artifact_substitution.Conf.dummy
~input_file:(Path.of_string "<memory>")
~input
~output
in
());
let result = Buffer.contents buf in
if result <> expected
then
fail
"Got invalid result!\nInput: \"%s\"\nExpected: \"%s\"\nResult: \"%s\""
(compress_string input)
(compress_string expected)
(compress_string result)
;;
(* {2 Test cases} *)
let () =
test "";
test "lkdjflskfjlksdf"
;;
let () =
test
(String.concat
~sep:""
[ "foo "; Artifact_substitution.encode (Repeat (2, "xyz")); " bar" ])
;;
let () =
let s = Artifact_substitution.encode (Repeat (2, "xyz")) in
let testf fmt = Printf.ksprintf test fmt in
testf "%s" s;
testf "%%%%%s%%%%" s;
for i = 0 to String.length s do
testf "%s%s" (String.sub s ~pos:0 ~len:i) s;
testf "%s%s" s (String.sub s ~pos:0 ~len:i);
testf "%s%s%s" (String.sub s ~pos:0 ~len:i) (String.sub s ~pos:0 ~len:i) s;
testf "%*s%s" (65536 - i) "" s;
testf "%*s%s%s" (65536 - i) "" (String.sub s ~pos:0 ~len:i) s
done
;;

View file

@ -0,0 +1,4 @@
(test
(name artifact_substitution)
(package dune-private-libs)
(libraries stdune dune_engine dune_rules fiber dune_re memo))

View file

@ -0,0 +1,10 @@
(executable
(name sexp_tests)
(modules sexp_tests)
(libraries stdune dune_lang))
(rule
(alias runtest)
(package dune-private-libs)
(action
(run ./sexp_tests.exe)))

View file

@ -0,0 +1,2 @@
directory = "qux"
archive(byte) = "qux.cma"

View file

@ -0,0 +1 @@
archive(byte) = "xyz.cma"

View file

@ -0,0 +1,3 @@
requires = "bar"
requires(ppx_driver) = "baz xyz"
exports = "baz wrong"

View file

@ -0,0 +1,17 @@
(executable
(name fswatch_win_tests)
(libraries fswatch_win unix stdune))
(rule
(alias fswatch_win_tests)
(deps
(sandbox always))
(action
(run ./fswatch_win_tests.exe)))
(alias
(name runtest)
(enabled_if
(= %{os_type} Win32))
(deps
(alias fswatch_win_tests)))

View file

@ -0,0 +1,308 @@
open Stdune
let remove_dot_slash s = String.drop_prefix s ~prefix:".\\" |> Option.value ~default:s
let create_file fn = Io.String_path.write_file fn ""
let mkdir fn = Unix.mkdir fn 0o777
type event =
{ action : string
; path : string
}
let dyn_of_event ev =
let action =
match Fswatch_win.Event.action ev with
| Added -> "added"
| Removed -> "removed"
| Modified -> "modified"
| Renamed_old -> "renamed_old"
| Renamed_new -> "renamed_new"
in
let path = remove_dot_slash (Fswatch_win.Event.path ev) in
Dyn.record [ "action", Dyn.string action; "path", Dyn.string path ]
;;
let dyn_of_event' { action; path } =
let path = remove_dot_slash path in
Dyn.record [ "action", Dyn.string action; "path", Dyn.string path ]
;;
let print_events events = print_endline (Dyn.to_string (Dyn.list Fun.id events))
let markdir = Filename.concat (Sys.getcwd ()) "mark"
let beginning_of_test = "BEGINNING_OF_TEST"
let end_of_test = "END_OF_TEST"
let watch, collect_events =
(* File used to mark the beginning and end of tests. *)
mkdir markdir;
let beginning_of_test_file = Filename.concat markdir beginning_of_test in
let end_of_test_file = Filename.concat markdir end_of_test in
create_file beginning_of_test_file;
create_file end_of_test_file;
let fswatch = Fswatch_win.create () in
let watch dir =
let dir =
if Filename.is_relative dir then Filename.concat (Sys.getcwd ()) dir else dir
in
Fswatch_win.add fswatch dir
in
watch markdir;
let rec collect_events acc = function
| [] ->
let events = Fswatch_win.wait fswatch ~sleep:0 in
collect_events acc events
| e :: events when Fswatch_win.Event.path e = end_of_test ->
if not (List.is_empty events)
then (
Printf.printf "***** Leftover events after end of test marker event *****\n";
print_events (List.map ~f:dyn_of_event events));
List.rev_map ~f:dyn_of_event acc
| ev :: events -> collect_events (ev :: acc) events
in
let collect_events () =
(* Mark the beginning of the current test *)
create_file end_of_test_file;
let events =
let events = Fswatch_win.wait fswatch ~sleep:0 in
(* List.iter ~f:(fun ev -> print_endline (Dyn.to_string (Fswatch_win.Event.to_dyn ev))) events; *)
match events with
| [] -> assert false
| e :: events when Fswatch_win.Event.path e = beginning_of_test ->
collect_events [] events
| events ->
Printf.printf "***** First event is not the beginning of test marker *****\n";
collect_events [] events
in
(* Mark the beginning of the next test *)
create_file beginning_of_test_file;
events
in
create_file beginning_of_test_file;
watch, collect_events
;;
(* Run a function in a sub-directory *)
let in_sub_dir =
let n = ref 0 in
fun f ->
incr n;
let dir = Printf.sprintf "test%d" !n in
mkdir dir;
Sys.chdir dir;
Exn.protect ~finally:(fun () -> Sys.chdir "..") ~f
;;
let check_events ~real_events expected_events =
let expected_events = List.map ~f:dyn_of_event' expected_events in
if real_events = expected_events
then ()
else (
print_endline "** FAILURE **";
print_endline "ACTUAL:";
print_events real_events;
print_endline "EXPECTED:";
print_events expected_events;
exit 1)
;;
let _ =
in_sub_dir
@@ fun () ->
let fn = "file" in
create_file fn;
watch ".";
create_file fn;
check_events ~real_events:(collect_events ()) [ { action = "modified"; path = fn } ]
;;
let fold_int n ~init ~f =
let rec loop i acc = if i = n then acc else loop (i + 1) (f i acc) in
loop 0 init
;;
type kind =
| File
| Dir
let rec gen_tree acc ~dir ~depth ~files_per_dir ~sub_dirs_per_dir =
let acc =
fold_int files_per_dir ~init:acc ~f:(fun n acc ->
let fn = Filename.concat dir (Printf.sprintf "f%d" (n + 1)) in
create_file fn;
(File, fn) :: acc)
in
if depth = 0
then acc
else
fold_int sub_dirs_per_dir ~init:acc ~f:(fun n acc ->
let dir = Filename.concat dir (Printf.sprintf "d%d" (n + 1)) in
let acc = (Dir, dir) :: acc in
mkdir dir;
gen_tree acc ~dir ~depth:(depth - 1) ~files_per_dir ~sub_dirs_per_dir)
;;
let gen_tree ~depth ~files_per_dir ~sub_dirs_per_dir =
List.rev (gen_tree [ Dir, "." ] ~dir:"." ~depth ~files_per_dir ~sub_dirs_per_dir)
;;
let _ =
(* Show that gen_tree generates filenames in the right order *)
in_sub_dir
@@ fun () ->
let entries =
List.map (gen_tree ~depth:1 ~files_per_dir:2 ~sub_dirs_per_dir:2) ~f:Stdlib.snd
in
(* List.iter ~f:print_endline entries; *)
assert (
List.map ~f:remove_dot_slash entries
= [ "."; "f1"; "f2"; "d1"; "d1\\f1"; "d1\\f2"; "d2"; "d2\\f1"; "d2\\f2" ])
;;
(* Return the expected set of inotify events *)
let gen_changes files =
List.iter files ~f:(function
| Dir, fn ->
let new_file = Filename.concat fn "new-file" in
let new_dir = Filename.concat fn "new-dir" in
create_file new_file;
mkdir new_dir;
Unix.rmdir new_dir;
Sys.remove new_file
| File, fn -> create_file fn)
;;
let setup1 ~depth ~files_per_dir ~sub_dirs_per_dir =
let files = gen_tree ~depth ~files_per_dir ~sub_dirs_per_dir in
watch ".";
files, collect_events
;;
let _ =
(* Check that FS events are reported chronologically *)
in_sub_dir
@@ fun () ->
let files, collect_events = setup1 ~depth:2 ~files_per_dir:3 ~sub_dirs_per_dir:2 in
gen_changes files;
check_events
~real_events:(collect_events ())
[ { action = "added"; path = "new-file" }
; { action = "added"; path = "new-dir" }
; { action = "removed"; path = "new-dir" }
; { action = "removed"; path = "new-file" }
; { action = "modified"; path = "f1" }
; { action = "modified"; path = "f2" }
; { action = "modified"; path = "f3" }
; { action = "added"; path = "d1\\new-file" }
; { action = "added"; path = "d1\\new-dir" }
; { action = "removed"; path = "d1\\new-dir" }
; { action = "removed"; path = "d1\\new-file" }
; { action = "modified"; path = "d1\\f1" }
; { action = "modified"; path = "d1\\f2" }
; { action = "modified"; path = "d1\\f3" }
; { action = "added"; path = "d1\\d1\\new-file" }
; { action = "added"; path = "d1\\d1\\new-dir" }
; { action = "removed"; path = "d1\\d1\\new-dir" }
; { action = "removed"; path = "d1\\d1\\new-file" }
; { action = "modified"; path = "d1\\d1\\f1" }
; { action = "modified"; path = "d1\\d1\\f2" }
; { action = "modified"; path = "d1\\d1\\f3" }
; { action = "added"; path = "d1\\d2\\new-file" }
; { action = "added"; path = "d1\\d2\\new-dir" }
; { action = "removed"; path = "d1\\d2\\new-dir" }
; { action = "removed"; path = "d1\\d2\\new-file" }
; { action = "modified"; path = "d1\\d2\\f1" }
; { action = "modified"; path = "d1\\d2\\f2" }
; { action = "modified"; path = "d1\\d2\\f3" }
; { action = "added"; path = "d2\\new-file" }
; { action = "added"; path = "d2\\new-dir" }
; { action = "removed"; path = "d2\\new-dir" }
; { action = "removed"; path = "d2\\new-file" }
; { action = "modified"; path = "d2\\f1" }
; { action = "modified"; path = "d2\\f2" }
; { action = "modified"; path = "d2\\f3" }
; { action = "added"; path = "d2\\d1\\new-file" }
; { action = "added"; path = "d2\\d1\\new-dir" }
; { action = "removed"; path = "d2\\d1\\new-dir" }
; { action = "removed"; path = "d2\\d1\\new-file" }
; { action = "modified"; path = "d2\\d1\\f1" }
; { action = "modified"; path = "d2\\d1\\f2" }
; { action = "modified"; path = "d2\\d1\\f3" }
; { action = "added"; path = "d2\\d2\\new-file" }
; { action = "added"; path = "d2\\d2\\new-dir" }
; { action = "removed"; path = "d2\\d2\\new-dir" }
; { action = "removed"; path = "d2\\d2\\new-file" }
; { action = "modified"; path = "d2\\d2\\f1" }
; { action = "modified"; path = "d2\\d2\\f2" }
; { action = "modified"; path = "d2\\d2\\f3" }
]
;;
(* Check interleaving more specifically *)
let _ =
in_sub_dir
@@ fun () ->
mkdir "a";
mkdir "b";
watch ".";
create_file "a\\x";
create_file "b\\x";
create_file "a\\y";
check_events
~real_events:(collect_events ())
[ { action = "added"; path = "a\\x" }
; { action = "added"; path = "b\\x" }
; { action = "added"; path = "a\\y" }
]
;;
let run cmd =
match
snd
(Unix.waitpid
[]
(Unix.create_process
(List.hd cmd)
(Array.of_list cmd)
Unix.stdin
Unix.stdout
Unix.stderr))
with
| WEXITED 0 -> ()
| _ -> assert false
;;
(* Check that ordering is respected when the changes are made by an external
process. Which is the assumption we are making for the fs sync mechanism of
the file watcher. *)
let _ =
in_sub_dir
@@ fun () ->
mkdir "_build";
mkdir "_build\\.sync";
watch ".";
let actions =
[ `Me; `Ext; `Me; `Ext; `Ext ] |> List.mapi ~f:(fun i who -> string_of_int i, who)
in
let actions = actions @ [ "_build\\.sync\\1", `Me ] in
let do_actions () =
List.iter actions ~f:(fun (fn, who) ->
match who with
| `Me -> create_file fn
| `Ext -> run [ "touch"; fn ])
in
do_actions ();
let real_events = collect_events () in
let expected_events =
[ { action = "added"; path = "0" }
; { action = "added"; path = "1" }
; { action = "modified"; path = "1" }
; { action = "added"; path = "2" }
; { action = "added"; path = "3" }
; { action = "modified"; path = "3" }
; { action = "added"; path = "4" }
; { action = "modified"; path = "4" }
; { action = "added"; path = "_build\\.sync\\1" }
]
in
check_events ~real_events expected_events
;;

View file

@ -0,0 +1 @@
SUPPORTS_SHARED_LIBRARIES=true

View file

@ -0,0 +1,5 @@
(test
(name gh637)
(package dune-private-libs)
(libraries stdune ocaml_config)
(deps Makefile.config))

View file

@ -0,0 +1,59 @@
open! Stdune
let pwd = Sys.getcwd ()
let valid_ocaml_config =
Printf.sprintf
{|version: 4.02.3
standard_library_default: %s
standard_library: %s
standard_runtime: /usr/bin/ocamlrun
ccomp_type: cc
bytecomp_c_compiler: gcc -O -fno-defer-pop -Wall -D_FILE_OFFSET_BITS=64 -D_REENTRANT -O -fPIC
bytecomp_c_libraries: -lm -ldl -lcurses -lpthread
native_c_compiler: gcc -O -Wall -D_FILE_OFFSET_BITS=64 -D_REENTRANT
native_c_libraries: -lm -ldl
native_pack_linker: ld -r -o
ranlib: ranlib
cc_profile: -pg
architecture: none
model: default
system: unknown
asm:
asm_cfi_supported: false
with_frame_pointers: false
ext_obj: .o
ext_asm: .s
ext_lib: .a
ext_dll: .so
os_type: Unix
default_executable_name: a.out
systhread_supported: true
host: mips-unknown-linux-gnu
target: mips-unknown-linux-gnu
exec_magic_number: Caml1999X011
cmi_magic_number: Caml1999I017
cmo_magic_number: Caml1999O010
cma_magic_number: Caml1999A011
cmx_magic_number: Caml1999Y014
cmxa_magic_number: Caml1999Z013
ast_impl_magic_number: Caml1999M016
ast_intf_magic_number: Caml1999N015
cmxs_magic_number: Caml2007D002
cmt_magic_number: Caml2012T004|}
pwd
pwd
;;
let () =
match
match valid_ocaml_config |> String.split_lines |> Ocaml_config.Vars.of_lines with
| Ok x -> Ocaml_config.make x
| Error msg -> Error (Ocamlc_config, msg)
with
| Error (_, e) -> failwith e
| Ok c ->
(* Check that [Ocaml_config.to_list] and [Ocaml_config.by_name] agree *)
List.iter (Ocaml_config.to_list c) ~f:(fun (name, v) ->
assert (Ocaml_config.by_name c name = Some v))
;;

View file

@ -0,0 +1,52 @@
open! Stdune
let () = Printexc.record_backtrace true
(* Test that all strings of length <= 3 such that [Dune_lang.Atom.is_valid s]
are recognized as atoms by the parser *)
let () =
for len = 0 to 3 do
let s = Bytes.create len in
for i = 0 to (1 lsl (len * 8)) - 1 do
if len > 0 then Bytes.set s 0 (Char.chr (i land 0xff));
if len > 1 then Bytes.set s 1 (Char.chr ((i lsr 4) land 0xff));
if len > 2 then Bytes.set s 2 (Char.chr ((i lsr 8) land 0xff));
let s = Bytes.unsafe_to_string s in
let parser_recognizes_as_atom =
match Dune_lang.Parser.parse_string ~fname:"" ~mode:Single s with
| exception _ -> false
| Atom (_, A s') -> s = s'
| _ -> false
in
let printed_as_atom =
match Dune_lang.atom_or_quoted_string s with
| Atom _ -> true
| _ -> false
in
let valid_dune_atom = Dune_lang.Atom.is_valid s in
if valid_dune_atom <> parser_recognizes_as_atom
then (
Printf.eprintf
"Dune_lang.Atom.is_valid error:\n\
- s = %S\n\
- Dune_lang.Atom.is_valid s = %B\n\
- parser_recognizes_as_atom = %B\n"
s
valid_dune_atom
parser_recognizes_as_atom;
exit 1);
if printed_as_atom && not parser_recognizes_as_atom
then (
Printf.eprintf
"Dune_lang.Atom.atom_or_quoted_string error:\n\
- s = %S\n\
- printed_as_atom = %B\n\
- parser_recognizes_as_atom = %B\n"
s
printed_as_atom
parser_recognizes_as_atom;
exit 1)
done
done
;;

View file

@ -0,0 +1 @@
FOO_BAR(tlc, env) = "my variable"