Source file expectation_intf.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
open! Base
open Types

module Definitions = struct
  module Insert_loc = struct
    (** Whether this expectation is tied to an AST node already present in the source, and
        the location information needed to determine where to insert corrections for this
        expectation *)
    type t =
      | Overwrite of
          { whole_node : Compact_loc.t
          ; payload : Compact_loc.t option
          }
          (** An expectation parsed from the test file and which should be overwritten by
          corrections. Corrections to just the payload should overwrite just the [payload]
          location, if present. If no [payload] location is present, or for corrections
          that change the entire node (e.g. a change from [[%expect _]] to
          [[%expect.unreachable]]), overwrite the [whole_node] loc. *)
      | Insert of Virtual_loc.t
          (** An expectation not parsed from the file that should be inserted into
          [Virtual_loc.loc] and is associated with a test whose body is at
          [Virtual_loc.body_loc] *)
  end

  module Behavior_type = struct
    (** The type of expect node *)
    type t =
      [ `Expect
      | `Unreachable
      ]
  end

  module Expect_reachability = struct
    (** Whether an expect node expresses an assertion that control flow passes through it
        every time a test is run *)
    type t =
      | Can_reach
          (** Test passes even if node is only reached on *some* executions of a test *)
      | Must_reach (** Test fails unless node is reached by *all* executions of a test *)
  end

  module On_unreachable = struct
    (** What should be done if this expectation is never reached in the test execution *)
    type t =
      | Silent (** Do nothing *)
      | Delete (** Delete this expectation from the source file *)
      | Replace_with_unreachable
          (** Replace this expectation with a [[%expect.unreachable]] node *)
  end

  module Behavior = struct
    (** A ['behavior_type t] describes how to handle a test node when running tests
        and writing corrections.

        ['behavior_type] determines the types of rewrites that are possible at this node.
        It is either [`Expect] (indicating that both corrections for unexpected output and
        rewrites for unreachability are possible) or [`Unreachable] (indicating that only
        corrections for unexpected output are possible).
    *)
    type _ t =
      | Expect :
          { payload : Output.Payload.t
          ; on_unreachable : On_unreachable.t
          ; reachability : Expect_reachability.t
          }
          -> [ `Expect ] t
      | Unreachable :
          { reachability_of_corrected : Expect_reachability.t
              (** The reachability of the node inserted if this unreachable node is
              unexpectedly reached *)
          }
          -> [ `Unreachable ] t
  end

  (** A [('behavior_type) t] carries information about how to run tests for a
      specific expect node and rewrite it in the source file if there are corrections. The
      ['behavior_type] type variable has the same meanings as in
      ['behavior_type Behavior.t].
  *)
  type 'behavior_type t =
    { position : Insert_loc.t
    ; behavior : 'behavior_type Behavior.t
    ; payload_type : Output.Type.t
    ; on_incorrect_output : String_node_format.Shape.t
        (** The name and syntax style of the extension point or attribute used to write
        corrections when receiving "incorrect" output for this test node. For each [t],
        there is only one such node. For example, if an [{%expect_exact||}] node is
        reached with incorrect output, it is always corrected to a different
        [{%expect_exact||}] node, and an [[%expect.unreachable]] that is reached is
        always corrected to an [[%expect]] node.

        Note that for a node that should be reachable, the correction when it is found to
        be unreachable is instead governed by [on_unreachable] in the [Expect] constructor
        of [behavior].
    *)
    ; inconsistent_outputs_message : string
    }
end

module type Expectation = sig
  include module type of struct
    include Definitions
  end

  module Insert_loc : sig
    include module type of struct
      include Insert_loc
    end

    val loc : Insert_loc.t -> Compact_loc.t
  end

  val with_behavior : 'old_behavior t -> 'new_behavior Behavior.t -> 'new_behavior t

  (** [formatter ~expect_node_formatting t] returns the [Output.Formatter.t] that formats
      test output according to the type ([exact] or [pretty]) of [t], using information
      about the location and payload of [t] for formatting. *)
  val formatter
    :  expect_node_formatting:Expect_node_formatting.t
    -> _ t
    -> Output.Formatter.t

  (** [[%expect _]] *)
  val expect
    :  formatting_flexibility:Expect_node_formatting.Flexibility.t
    -> node_loc:Compact_loc.t
    -> located_payload:(Output.Payload.t * Compact_loc.t) option
    -> [ `Expect ] t

  (** [[%expect_exact _]] *)
  val expect_exact
    :  formatting_flexibility:Expect_node_formatting.Flexibility.t
    -> node_loc:Compact_loc.t
    -> located_payload:(Output.Payload.t * Compact_loc.t) option
    -> [ `Expect ] t

  (** [[%expect.unreachable]] *)
  val expect_unreachable : node_loc:Compact_loc.t -> [ `Unreachable ] t

  (** [[@@expect.uncaught_exn _]] *)
  val expect_uncaught_exn
    :  formatting_flexibility:Expect_node_formatting.Flexibility.t
    -> node_loc:Compact_loc.t
    -> located_payload:(Output.Payload.t * Compact_loc.t) option
    -> [ `Expect ] t

  (** Runtime representation of the implicit [[%expect {||}]] at the end of every expect
      test. *)
  val expect_trailing : insert_loc:Virtual_loc.t -> [ `Expect ] t

  (** Runtime representation of the assertion that a test does not produce uncaught
      exceptions, which a user implicitly makes by omitting an [[@@expect.uncaught_exn _]]
      attribute. *)
  val expect_no_uncaught_exn : insert_loc:Virtual_loc.t -> [ `Unreachable ] t

  module For_apply_style : sig
    type format_payload :=
      expect_node_formatting:Expect_node_formatting.t
      -> payload_loc:Compact_loc.t
      -> node_loc:Compact_loc.t
      -> String_node_format.Delimiter.t
      -> string
      -> string option

    val format_expect_payload : format_payload
    val format_uncaught_exn_payload : format_payload
  end
end