package tezt
Test framework for unit tests, integration tests, and regression tests
Install
Dune Dependency
Authors
Maintainers
Sources
tezt-4.1.0.tar.bz2
md5=88c2d9d3da75ff554599bc34cbf5acbe
sha512=e60294514ecc4a989ce663ebb306e56f654dcfaffb7dbe5e3f05f5a13c9c2ff64dadde4a77b0d9a8567a76a6a7a2b25e0940ccd2a956ffcb85ff9300bfebe3bc
doc/src/tezt.core/regression.ml.html
Source file regression.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2020 Metastate AG <hello@metastate.dev> *) (* Copyright (c) 2020-2023 Nomadic Labs <contact@nomadic-labs.com> *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) (* to deal in the Software without restriction, including without limitation *) (* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) (* and/or sell copies of the Software, and to permit persons to whom the *) (* Software is furnished to do so, subject to the following conditions: *) (* *) (* The above copyright notice and this permission notice shall be included *) (* in all copies or substantial portions of the Software. *) (* *) (* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) (* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) (* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) (* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) (* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) (* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) (* DEALINGS IN THE SOFTWARE. *) (* *) (*****************************************************************************) open Base let capture_output : (string -> unit) option ref = ref None (* Capture a string into a regression output. *) let capture ?(eol = true) line = match !capture_output with | None -> () | Some output -> output line ; if eol then output "\n" let hooks : Process_hooks.t = { on_spawn = (fun command arguments -> let message = Log.quote_shell_command command arguments in capture "" ; capture message); on_log = capture; } let run_and_capture_output ~capture (f : unit -> 'a Lwt.t) = capture_output := Some capture ; Lwt.finalize f @@ fun () -> capture_output := None ; unit (* Run [f] and capture the output of ran processes into the [output_file]. *) let run_and_capture_output_to_file ~output_file (f : unit -> 'a Lwt.t) = let rec create_parent filename = let parent = Filename.dirname filename in if String.length parent < String.length filename then ( create_parent parent ; if not (Sys.file_exists parent) then try Unix.mkdir parent 0o755 with Unix.Unix_error (EEXIST, _, _) -> (* Can happen with [-j] in particular. *) ()) in create_parent output_file ; let channel = open_out output_file in capture_output := Some (output_string channel) ; Lwt.finalize f @@ fun () -> capture_output := None ; close_out channel ; unit (* Map from output directories to output files. Output directories are directories that are supposed to only contain output files. Subdirectories of output directories do not appear as keys in this map. In the map, output files can be in subdirectories (i.e. they can contain '/'). *) let output_dirs_and_files : String_set.t String_map.t ref = ref String_map.empty let register ~__FILE__ ~title ~ ?file f = let = "regression" :: tags in let output_dir = project_root // Filename.dirname __FILE__ // "expected" in let relative_output_file = let file = match file with | Some file -> file | None -> (* Sanitize title. We exclude ':' because of Windows. *) let sanitize_char = function | ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '-' | '.' | ' ' | '(' | ')' ) as x -> x | _ -> '-' in let full = String.map sanitize_char title in let max_length = 80 in if String.length full > max_length then String.sub full 0 max_length else full in Filename.basename __FILE__ // (file ^ ".out") in let old_relative_output_files = String_map.find_opt output_dir !output_dirs_and_files |> Option.value ~default:String_set.empty in let stored_full_output_file = output_dir // relative_output_file in if String_set.mem relative_output_file old_relative_output_files then invalid_arg (sf "the output of test %S would be stored in %S, which is already used \ by another test" title stored_full_output_file) ; output_dirs_and_files := String_map.add output_dir (String_set.add relative_output_file old_relative_output_files) !output_dirs_and_files ; Test.register ~__FILE__ ~title ~tags @@ fun () -> (* when the stored output doesn't already exists, must reset regressions *) if not (Sys.file_exists stored_full_output_file || Cli.Options.reset_regressions) then Test.fail "Regression output file not found: %s. To generate it, use: \ --reset-regressions --title %s" (Log.quote_shell stored_full_output_file) (Log.quote_shell title) ; if Cli.Options.reset_regressions then run_and_capture_output_to_file ~output_file:stored_full_output_file f else let* after = let buffer = Buffer.create 512 in let* () = run_and_capture_output ~capture:(Buffer.add_string buffer) f in Buffer.contents buffer |> String.split_on_char '\n' |> Array.of_list |> return in let before = read_file stored_full_output_file |> String.split_on_char '\n' |> Array.of_list in let diff = Diff.arrays ~equal:String.equal ~before:stored_full_output_file ~after:"captured" before after in if diff.different then ( Diff.log (Diff.reduce_context diff) ; Test.fail "Regression output file contains differences: %s. To accept the \ differences, use: --reset-regressions --title %s" (Log.quote_shell stored_full_output_file) (Log.quote_shell title)) ; unit let check_unknown_output_files output_dir relative_output_files = let full_output_files = String_set.map (fun relative_output_file -> output_dir // relative_output_file) relative_output_files in let found_unknown = ref false in let mode = Cli.Options.on_unknown_regression_files_mode in let log_unused = match mode with Fail -> Log.error | _ -> Log.warn in let rec browse path = let handle_file filename = let full = path // filename in match Sys.is_directory full with | exception Sys_error _ -> (* If we can't browse, ignore. *) () | true -> browse full | false -> if not (String_set.mem full full_output_files) then if mode = Delete then try Sys.remove full ; Log.report "Deleted file: %s" full with Sys_error message -> Log.warn "Failed to delete file: %s" message else ( log_unused "%s is not used by any test and can be deleted." full ; found_unknown := true) in let try_to_read_dir () = try Sys.readdir path with Sys_error _ -> (* Mostly happens if [path] does not exist or is not a directory, in which case we have nothing to browse. Could also happen because of permissions or other system issues, but since this is just a check to help developers, we don't want to bother them if this happens. *) [||] in Array.iter handle_file (try_to_read_dir ()) ; (* Check whether directory is empty now that we may have deleted files. *) match Sys.readdir path with | exception Sys_error _ -> () | [||] -> if mode = Delete then try Sys.rmdir path ; Log.report "Deleted directory: %s" path with Sys_error message -> Log.warn "Failed to delete directory: %s" message else ( log_unused "%s is empty and can be deleted." path ; found_unknown := true) | _ -> () in browse output_dir ; !found_unknown let () = (* We should not run [check_unknown_output_files] before [Clap.close], and we cannot run it from the [Test] module because it would create a circular dependency. *) Test.before_test_run @@ fun () -> let check_all_unknown_output_files () = String_map.fold (fun output_dir relative_output_files found_unknown -> check_unknown_output_files output_dir relative_output_files || found_unknown) !output_dirs_and_files false in let warn_unknown_output_files () = Log.warn "Use --on-unknown-regression-files delete to delete those files and/or \ directories." in match Cli.Options.on_unknown_regression_files_mode with | Ignore -> () | Warn -> let found_unknown = check_all_unknown_output_files () in if found_unknown then warn_unknown_output_files () | Fail -> let found_unknown = check_all_unknown_output_files () in if found_unknown then ( warn_unknown_output_files () ; exit 1) | Delete -> let _ = check_all_unknown_output_files () in (* Unknown output files are deleted inside [check_unknown_output_files] so we do nothing here. *) exit 0
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>