diff --git a/test/test_flan.ml b/test/test_flan.ml index a6c7b8d..a147e76 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -1378,6 +1378,105 @@ let () = check "nor is the other half of the collision" (not (List.exists (fun l -> contains l "\"spin2d\"") produced)); + (* ── The config beside the header (Cimport.read_config) ────────── *) + + (* Why there is a config at all: the generated declarations are committed, so + a hand-edit to them is destroyed by the next regeneration and the edit has + to live somewhere regeneration reads instead. These are the two things it + can say. *) + let with_config config = + let taken = Hashtbl.create 16 in + List.iter + (fun d -> + match Ast.declared_name d with + | Some n -> Hashtbl.replace taken n () + | None -> ()) + fixture_ds; + let known_structs = + List.filter_map + (fun (d : Ast.decl) -> + match d.Ast.d with Ast.Defstruct (n, _) -> Some n | _ -> None) + fixture_ds + and known_enums = + List.filter_map + (fun (d : Ast.decl) -> + match d.Ast.d with Ast.Defenum (n, _) -> Some n | _ -> None) + fixture_ds + in + let i, _, _ = + Cimport.header ~loc:Loc.unknown ~header:"headers/sample.h" ~flags:[] + ~known_structs ~known_enums ~taken ~bound_syms:[] ~config + in + (List.map Cimport.decl_source i.Cimport.decls, i.Cimport.hidden) + in + + let lines, hidden = + with_config { Cimport.excludes = [ "set_seed" ]; renames = [] } + in + check "an excluded symbol is not generated" + (not (List.exists (fun l -> contains l "\"set_seed\"") lines)); + (* Not silently absent. "there is no such binding" and "the package decided + against this binding" are different answers and a reader gets the second, + which is what [hidden] is for. *) + check "and it says it was excluded rather than going quiet" + (List.exists + (fun (n, why) -> n = "set-seed" && contains why "excluded by the package") + hidden); + + let lines, _ = + with_config { Cimport.excludes = [ "add_*" ]; renames = [] } + in + check "an exclude pattern matches by prefix" + (not (List.exists (fun l -> contains l "\"add_ints\"") lines)); + check "and leaves everything it does not match" + (List.exists (fun l -> contains l "\"set_seed\"") lines); + + let lines, _ = + with_config + { Cimport.excludes = []; renames = [ ("set_seed", "seed!") ] } + in + (* The C symbol is kept verbatim, so an override changes the Flan face and + nothing else — which is what makes it safe to spell a predicate the way + Lisp spells one. *) + check "a name override is the Flan name, and the C symbol is untouched" + (List.mem "(declare-c seed! [seed u32] \"set_seed\")" lines); + + (* The collision above is refused because neither Spin2D nor spin2d may take + [spin-2d] by an accident of header order. Naming one of them is the way + out, and it is the reason the kebab rule is consulted in exactly one + place: the groups are computed on the name a function will really take, + so the rename dissolves the group rather than leaving both refused. *) + let lines, hidden = + with_config + { Cimport.excludes = []; renames = [ ("Spin2D", "spin-2d-upper") ] } + in + check "a rename resolves a collision for both halves" + (List.exists (fun l -> contains l "\"Spin2D\"") lines + && List.exists (fun l -> contains l "\"spin2d\"") lines); + check "and the collision is no longer refused" + (not (List.mem_assoc "spin-2d" hidden)); + + (* The file, since a typo in it would otherwise show up as a binding under + the wrong name. A line that is neither directive is an error rather than a + line quietly skipped. *) + let config_file text = + let f = Filename.temp_file "flan-bindings" "" in + let ch = open_out f in + output_string ch text; + close_out ch; + f + in + let c = Cimport.read_config (config_file "# a comment\n\nexclude Mem*\nname IsWindowReady window-ready?\n") in + check "read_config reads an exclude" (c.Cimport.excludes = [ "Mem*" ]); + check "read_config reads a name override" + (c.Cimport.renames = [ ("IsWindowReady", "window-ready?") ]); + check "a missing config is no exclusions and no overrides" + (Cimport.read_config "no-such-bindings-file" = Cimport.no_config); + check "a line that is neither directive is refused" + (match Cimport.read_config (config_file "rename Foo bar\n") with + | _ -> false + | exception Loc.Error (_, m) -> contains m "and this is neither"); + (* A refused name is a name that exists and cannot be had — Zig's failDecl, which Load.refuse_hidden already implements for main. Nothing may be in both lists, or asking for a name that works would report that it does