package ppx_quick_test
Spiritual equivalent of let%expect_test, but for property based tests as an ergonomic wrapper to write quickcheck tests.
Install
Dune Dependency
Authors
Maintainers
Sources
v0.17.0.tar.gz
sha256=d9556f991f7a75fb534a4a808fed3a18d0fd7ed55ecaa9a9bfefe9867d73b0d8
doc/src/ppx_quick_test.expander/quick_test_attributes.ml.html
Source file quick_test_attributes.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 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
open! Core open Ppxlib module String_constant = struct type t = { string_value : string ; location : location ; delimiter : string option } end module Sexp_examples = struct type t = | Provided of { string_constants : String_constant.t list ; expression_placement_cnum : int } | NotProvided end module Shrinker = struct type t = | Custom of expression | Default of core_type end module Generator = struct type t = | Custom of expression | Default of core_type end module Attribute_name = struct (* The names of the attributes that are written by the user in the form [@parameter_name] *) type t = | Config | Cr | Examples | Hide_positions | Generator | Shrinker | Remember_failures | Remember_failures_ignore let to_string t = let prefix = "quick_test" in let body = match t with | Config -> "config" | Cr -> "cr" | Examples -> "examples" | Hide_positions -> "hide_positions" | Generator -> "generator" | Shrinker -> "shrinker" | Remember_failures -> "remember_failures" | Remember_failures_ignore -> "remember_failures.ignore" in [%string "%{prefix}.%{body}"] ;; let pass_throughs = [ Config, Quick_test_parameter.Config ; Cr, Quick_test_parameter.Cr ; Hide_positions, Quick_test_parameter.Hide_positions ; Examples, Quick_test_parameter.Examples ] ;; let test_scoped = [ Config; Cr; Examples; Remember_failures; Remember_failures_ignore; Hide_positions ] ;; let type_scoped = [ Generator; Shrinker ] end type t = { pass_through_attrs : (Quick_test_parameter.t * expression) list ; sexp_examples : Sexp_examples.t ; generators : Generator.t list ; shrinkers : Shrinker.t list } module Parse_result = struct type nonrec t = { new_pattern : pattern ; new_parameters : (pattern * core_type) list ; attributes : t } end let declare_single_expr_attribute (name : Attribute_name.t) ~context = Attribute.declare (Attribute_name.to_string name) context Ast_pattern.(single_expr_payload __) Fn.id ;; let pass_through_attributes = Attribute_name.pass_throughs |> List.map ~f:(fun (attribute_name, parameter_name) -> ( parameter_name , declare_single_expr_attribute attribute_name ~context:Attribute.Context.Pattern )) ;; let sexp_examples_attribute = Attribute.declare_with_attr_loc (Attribute_name.to_string Remember_failures) Attribute.Context.Pattern Ast_pattern.(alt_option (single_expr_payload __) (pstr nil)) (fun ~attr_loc e -> attr_loc, e) ;; let generator_attribute = declare_single_expr_attribute Attribute_name.Generator ~context:Attribute.Context.Core_type ;; let shrinker_attribute = declare_single_expr_attribute Attribute_name.Shrinker ~context:Attribute.Context.Core_type ;; let assert_no_unused_attributes pattern = let unused_attributes = Attribute.collect_unused_attributes_errors#pattern pattern [] in match unused_attributes with | [] -> () | error :: _ -> let format_names names = names |> List.map ~f:Attribute_name.to_string |> List.map ~f:(Format.sprintf "* [@%s]") |> String.concat ~sep:"\n" in let message = [%string {|%{Location.Error.message error} "ppx_quick_test" found an unexpected attribute. Supported test-scoped attributes: %{format_names Attribute_name.test_scoped} Supported type-scoped attributes: %{format_names Attribute_name.type_scoped} For example: let%quick_test _ [@quick_test.hide_positions true] = fun (x : int [@quick_test.generator Base_quickcheck.Generator.int_uniform]) -> assert ( * x >= 0) |}] in let error = Location.Error.set_message error message in Location.Error.raise error ;; let parse_attribute_from_context ~context ~attribute = Attribute.consume_res attribute context |> Result.map ~f:(function | Some (new_context, expr) -> new_context, Some expr | None -> context, None) |> function | Ok res -> res | Error error_list -> let name = Attribute.name attribute in let error = Stdppx.NonEmptyList.hd error_list in let message = [%string {|"ppx_quick_test" found incorrect use of attribute `%{name}': %{Location.Error.message error}. Example of correct usage: {[ let%%quick_test "my test" [@quick_test.cr CR.CR_someday] = fun (x : int) -> assert (x * x >= 0) ]} |}] in let error = Location.Error.set_message error message in Location.Error.raise error ;; let parse_pass_through_attributes_from_pattern pattern = let pattern, attributes = List.fold_map pass_through_attributes ~init:pattern ~f:(fun pattern (param_name, attribute) -> let pattern, expr = parse_attribute_from_context ~context:pattern ~attribute in pattern, Option.map expr ~f:(fun expr -> param_name, expr)) in let attributes = List.filter_opt attributes in pattern, attributes ;; let parse_string_constant_from_expression expression = match expression.pexp_desc with | Pexp_constant (Pconst_string (contents, loc, delimiter)) -> { String_constant.string_value = contents; location = loc; delimiter } | _ -> Location.raise_errorf ~loc:expression.pexp_loc {|"ppx_quick_test" expected this expression to be a string literal constant|} ;; let parse_expression_from_unlabeled_argument argument = match fst argument with | Nolabel -> snd argument | _ -> Location.raise_errorf ~loc:(snd argument).pexp_loc {|"ppx_quick_test" expected this argument to unlabeled|} ;; let parse_string_constant_list_from_expression expr = Option.value_map ~default:[] expr ~f:(fun expr -> match expr.pexp_desc with | Pexp_apply (first, rest) -> let exprs = first :: List.map rest ~f:parse_expression_from_unlabeled_argument in List.map exprs ~f:parse_string_constant_from_expression | _ -> [ parse_string_constant_from_expression expr ]) ;; let parse_sexp_examples_attribute pattern = let pattern, sexp_examples_payload = parse_attribute_from_context ~context:pattern ~attribute:sexp_examples_attribute in let sexp_examples = match sexp_examples_payload with | None -> Sexp_examples.NotProvided | Some (attr_location, expr) -> Sexp_examples.Provided { string_constants = parse_string_constant_list_from_expression expr ; expression_placement_cnum = (match expr with | Some expr -> expr.pexp_loc.loc_end.pos_cnum | None -> attr_location.loc_end.pos_cnum - 1) } in pattern, sexp_examples ;; let parse_generators_and_shrinkers parameters = let resolve_generator ~default ~generator_payload = match generator_payload with | None -> Generator.Default default | Some expr -> Generator.Custom expr in let resolve_shrinker ~default ~shrinker_payload = match shrinker_payload with | None -> Shrinker.Default default | Some expr -> Shrinker.Custom expr in let parse_generator_and_shrinker (pattern, type_) = let type_, generator_payload = parse_attribute_from_context ~context:type_ ~attribute:generator_attribute in let type_, shrinker_payload = parse_attribute_from_context ~context:type_ ~attribute:shrinker_attribute in let generator = resolve_generator ~default:type_ ~generator_payload in let shrinker = resolve_shrinker ~default:type_ ~shrinker_payload in (pattern, type_), generator, shrinker in parameters |> List.map ~f:parse_generator_and_shrinker |> List.unzip3 ;; let parse ~pattern ~parameters = let pattern, pass_through_attrs = parse_pass_through_attributes_from_pattern pattern in let pattern, sexp_examples = parse_sexp_examples_attribute pattern in let parameters, generators, shrinkers = parse_generators_and_shrinkers parameters in assert_no_unused_attributes pattern; let attributes = { pass_through_attrs; sexp_examples; generators; shrinkers } in { Parse_result.new_pattern = pattern; new_parameters = parameters; attributes } ;; let expand_string_constant_to_expression { String_constant.string_value; location = loc; delimiter } = let open (val Ast_builder.make loc) in pexp_constant (Pconst_string (string_value, loc, delimiter)) ;; let create_list_expression expr_list ~loc = let open (val Ast_builder.make loc) in let list_expr = List.fold_right expr_list ~init:[%expr []] ~f:(fun list_elem acc -> [%expr [%e list_elem] :: [%e acc]]) in list_expr ;; let create_int_expression n ~loc = let open (val Ast_builder.make loc) in Pconst_integer (Int.to_string n, None) |> pexp_constant ;; let expand_not_provided_sexp_examples_expression ~loc = let open (val Ast_builder.make loc) in [%expr Ppx_quick_test_runtime_lib.Sexp_examples.NotProvided] ;; let expand_provided_sexp_examples_expression ~loc ~input_type ~string_constants ~expression_placement_cnum = let open (val Ast_builder.make loc) in let string_constant_exprs = List.map string_constants ~f:expand_string_constant_to_expression in [%expr Ppx_quick_test_runtime_lib.Sexp_examples.Provided { sexp_strings = [%e create_list_expression ~loc string_constant_exprs] ; of_sexp = [%of_sexp: [%t input_type]] ; expression_placement_cnum = [%e create_int_expression ~loc expression_placement_cnum] }] ;; let expand_sexp_examples_argument sexp_examples ~loc ~input_type = let open (val Ast_builder.make loc) in let open Merlin_helpers in let expr = match sexp_examples with | Sexp_examples.NotProvided -> expand_not_provided_sexp_examples_expression ~loc | Sexp_examples.Provided { string_constants : String_constant.t list; expression_placement_cnum : int } -> expand_provided_sexp_examples_expression ~loc ~input_type ~string_constants ~expression_placement_cnum in Quick_test_parameter.Sexp_examples, hide_expression expr ;; let expand_generator_argument (generators : Generator.t list) ~loc = let open (val Ast_builder.make loc) in let open Merlin_helpers in let generator_type = generators |> List.map ~f:(function | Default type_ -> type_ | Custom expr -> [%type: [%custom [%e expr]]]) |> ptyp_tuple in let expr = hide_expression [%expr [%quickcheck.generator: [%t generator_type]]] in Quick_test_parameter.Generator, expr ;; let expand_shrinker_argument (shrinkers : Shrinker.t list) ~loc = let open (val Ast_builder.make loc) in let open Merlin_helpers in let shrinkers_type = shrinkers |> List.map ~f:(function | Default type_ -> type_ | Custom expr -> [%type: [%custom [%e expr]]]) |> ptyp_tuple in let expr = hide_expression [%expr [%quickcheck.shrinker: [%t shrinkers_type]]] in Quick_test_parameter.Shrinker, expr ;; let expand_to_args t ~loc ~input_type = let sexp_examples_arg = expand_sexp_examples_argument t.sexp_examples ~loc ~input_type in let generator_arg = expand_generator_argument t.generators ~loc in let shrinker_arg = expand_shrinker_argument t.shrinkers ~loc in let args = generator_arg :: shrinker_arg :: sexp_examples_arg :: t.pass_through_attrs in args ;;
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>