Pure OCaml Yaml 1.2 reader and writer using Bytesrw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Generic test suite loader - parameterized by file I/O operations *) 7 8(** Test case representation *) 9type test_case = { 10 id : string; 11 name : string; 12 yaml : string; 13 tree : string option; 14 json : string option; 15 fail : bool; 16} 17 18(** Module type for file I/O operations *) 19module type FILE_IO = sig 20 (** Context type for file operations (unit for sync, ~fs for Eio) *) 21 type ctx 22 23 (** Read a file, returning None if it doesn't exist or can't be read *) 24 val read_file : ctx -> string -> string option 25 26 (** Check if a path exists and is a regular file *) 27 val file_exists : ctx -> string -> bool 28 29 (** Check if a path exists and is a directory *) 30 val is_directory : ctx -> string -> bool 31 32 (** List directory entries *) 33 val read_dir : ctx -> string -> string list 34end 35 36(** Create a test loader from file I/O operations *) 37module Make (IO : FILE_IO) = struct 38 type test_case = { 39 id : string; 40 name : string; 41 yaml : string; 42 tree : string option; 43 json : string option; 44 fail : bool; 45 } 46 47 let read_file_required ctx path = 48 match IO.read_file ctx path with 49 | Some s -> s 50 | None -> "" 51 52 (** Load a single test from a directory *) 53 let load_test_dir ctx base_id dir_path = 54 let name_file = Filename.concat dir_path "===" in 55 let yaml_file = Filename.concat dir_path "in.yaml" in 56 let tree_file = Filename.concat dir_path "test.event" in 57 let json_file = Filename.concat dir_path "in.json" in 58 let error_file = Filename.concat dir_path "error" in 59 60 (* Must have in.yaml to be a valid test *) 61 if not (IO.file_exists ctx yaml_file) then None 62 else 63 let name = match IO.read_file ctx name_file with 64 | Some s -> String.trim s 65 | None -> base_id 66 in 67 let yaml = read_file_required ctx yaml_file in 68 let tree = IO.read_file ctx tree_file in 69 let json = IO.read_file ctx json_file in 70 let fail = IO.file_exists ctx error_file in 71 Some { id = base_id; name; yaml; tree; json; fail } 72 73 (** Load tests from a test ID directory (may have subdirectories for variants) *) 74 let load_test_id ctx test_suite_path test_id = 75 let dir_path = Filename.concat test_suite_path test_id in 76 if not (IO.is_directory ctx dir_path) then [] 77 else 78 let entries = IO.read_dir ctx dir_path in 79 (* Check if this directory has variant subdirectories (00, 01, etc.) *) 80 let has_variants = List.exists (fun e -> 81 let subdir = Filename.concat dir_path e in 82 IO.is_directory ctx subdir && 83 String.length e >= 2 && 84 e.[0] >= '0' && e.[0] <= '9' 85 ) entries in 86 87 if has_variants then 88 (* Load each variant subdirectory *) 89 let variants = entries 90 |> List.filter (fun e -> 91 let subdir = Filename.concat dir_path e in 92 IO.is_directory ctx subdir && 93 String.length e >= 2 && 94 e.[0] >= '0' && e.[0] <= '9') 95 |> List.sort String.compare 96 in 97 List.filter_map (fun variant -> 98 let variant_path = Filename.concat dir_path variant in 99 let variant_id = Printf.sprintf "%s:%s" test_id variant in 100 load_test_dir ctx variant_id variant_path 101 ) variants 102 else 103 (* Single test in this directory *) 104 match load_test_dir ctx test_id dir_path with 105 | Some t -> [t] 106 | None -> [] 107 108 (** Load all tests from a test suite directory *) 109 let load_directory ctx test_suite_path = 110 if not (IO.is_directory ctx test_suite_path) then [] 111 else 112 let entries = IO.read_dir ctx test_suite_path in 113 let test_ids = entries 114 |> List.filter (fun e -> 115 IO.is_directory ctx (Filename.concat test_suite_path e) && 116 String.length e >= 4 && 117 e.[0] >= '0' && e.[0] <= 'Z') 118 |> List.sort String.compare 119 in 120 List.concat_map (load_test_id ctx test_suite_path) test_ids 121end