Split error codes for read/write on array access and object access
[hiphop-php.git] / hphp / hack / test / unit / utils / errors_test.ml
blob3463d6246797326e275adfac3ecb6451542d4d18
1 (**
2 * Tests documenting various invariants about the order of things coming out
3 * of Errors module. Some of them are __probably__ not necessary for
4 * correctness, but if you break them you will still change a bunch of tests
5 * outputs and will have to spend time wondering whether the changes are
6 * significant or not.
7 **)
9 open Hh_core
11 let error_list_to_string_buffer buf x =
12 List.iter x ~f:(fun error ->
13 Printf.bprintf buf "%s\n" Errors.(error |> to_absolute |> to_string))
15 let error_list_to_string errors =
16 let buf = Buffer.create 1024 in
17 error_list_to_string_buffer buf errors;
18 Buffer.contents buf
20 let create_path x = Relative_path.(create Root ("/" ^ x))
22 let error_in file = Errors.parsing_error (Pos.make_from (create_path file), "")
24 let expect_error_in =
25 Printf.sprintf "File \"/%s\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
27 let test_do () =
28 let (errors, ()) =
29 Errors.do_ (fun () ->
30 error_in "A";
31 error_in "B";
32 ())
34 let expected = expect_error_in "A" ^ expect_error_in "B" in
35 Asserter.String_asserter.assert_equals
36 expected
37 (Errors.get_error_list errors |> error_list_to_string)
38 "Errors should be returned from do_ in the order they were added";
39 Asserter.String_asserter.assert_equals
40 expected
41 (Errors.get_sorted_error_list errors |> error_list_to_string)
42 "get_sorted_error_list should sort errors by filename";
43 true
45 let expected_unsorted =
46 {|File "/FileWithErrors.php", line 1, characters 4-7:
47 This value is not a valid key type for this container (Typing[4298])
48 File "/C2", line 0, characters 0-0:
49 This container is C2_Type
50 File "/K2", line 0, characters 0-0:
51 K2_Type cannot be used as a key for C2_Type
53 File "/FileWithErrors.php", line 1, characters 4-7:
54 This value is not a valid key type for this container (Typing[4298])
55 File "/C1", line 0, characters 0-0:
56 This container is C1_Type
57 File "/K1", line 0, characters 0-0:
58 K1_Type cannot be used as a key for C1_Type
60 File "/FileWithErrors.php", line 0, characters 0-0:
61 (Parsing[1002])
63 File "/FileWithErrors.php", line 1, characters 4-7:
64 This value is not a valid key type for this container (Typing[4298])
65 File "/C2", line 0, characters 0-0:
66 This container is C2_Type
67 File "/K2", line 0, characters 0-0:
68 K2_Type cannot be used as a key for C2_Type
70 File "/FileWithErrors.php", line 1, characters 4-7:
71 This value is not a valid key type for this container (Typing[4298])
72 File "/C1", line 0, characters 0-0:
73 This container is C1_Type
74 File "/K1", line 0, characters 0-0:
75 K1_Type cannot be used as a key for C1_Type
79 let expected_sorted =
80 {|File "/FileWithErrors.php", line 0, characters 0-0:
81 (Parsing[1002])
83 File "/FileWithErrors.php", line 1, characters 4-7:
84 This value is not a valid key type for this container (Typing[4298])
85 File "/C1", line 0, characters 0-0:
86 This container is C1_Type
87 File "/K1", line 0, characters 0-0:
88 K1_Type cannot be used as a key for C1_Type
90 File "/FileWithErrors.php", line 1, characters 4-7:
91 This value is not a valid key type for this container (Typing[4298])
92 File "/C2", line 0, characters 0-0:
93 This container is C2_Type
94 File "/K2", line 0, characters 0-0:
95 K2_Type cannot be used as a key for C2_Type
99 let test_get_sorted_error_list () =
100 let (errors, ()) =
101 Errors.do_ (fun () ->
102 error_in "B";
103 error_in "A";
106 let expected = expect_error_in "B" ^ expect_error_in "A" in
107 Asserter.String_asserter.assert_equals
108 expected
109 (Errors.get_error_list errors |> error_list_to_string)
110 "Errors should be returned from do_ in the order they were added";
112 let expected = expect_error_in "A" ^ expect_error_in "B" in
113 Asserter.String_asserter.assert_equals
114 expected
115 (Errors.get_sorted_error_list errors |> error_list_to_string)
116 "get_sorted_error_list should sort errors by filename";
118 let file_with_errors = create_path "FileWithErrors.php" in
119 let err_pos =
120 Pos.make_from_lnum_bol_cnum
121 ~pos_file:file_with_errors
122 ~pos_start:(1, 5, 8)
123 ~pos_end:(2, 10, 12)
125 Printf.printf "%s" (Pos.print_verbose_relative err_pos);
126 let container_pos1 = Pos.make_from (create_path "C1") in
127 let container_pos2 = Pos.make_from (create_path "C2") in
128 let key_pos1 = Pos.make_from (create_path "K1") in
129 let key_pos2 = Pos.make_from (create_path "K2") in
130 let (errors, ()) =
131 Errors.do_with_context file_with_errors Errors.Typing (fun () ->
132 Errors.invalid_arraykey_read
133 err_pos
134 (container_pos2, "C2_Type")
135 (key_pos2, "K2_Type");
136 Errors.invalid_arraykey_read
137 err_pos
138 (container_pos1, "C1_Type")
139 (key_pos1, "K1_Type");
140 error_in "FileWithErrors.php";
141 Errors.invalid_arraykey_read
142 err_pos
143 (container_pos2, "C2_Type")
144 (key_pos2, "K2_Type");
145 Errors.invalid_arraykey_read
146 err_pos
147 (container_pos1, "C1_Type")
148 (key_pos1, "K1_Type");
151 Asserter.String_asserter.assert_equals
152 expected_unsorted
153 (Errors.get_error_list errors |> error_list_to_string)
154 "Errors should be returned in the order they were added";
156 Asserter.String_asserter.assert_equals
157 expected_sorted
158 (Errors.get_sorted_error_list errors |> error_list_to_string)
159 "get_sorted_error_list should sort errors by position, code, and warrant";
161 true
163 let test_try () =
164 let error_ref = ref None in
165 let () =
166 Errors.try_
167 (fun () ->
168 error_in "A";
169 error_in "B";
171 (fun error ->
172 error_ref := Some error;
175 let expected = expect_error_in "A" in
176 match !error_ref with
177 | None -> failwith "Expected error handler to run"
178 | Some e ->
179 Asserter.String_asserter.assert_equals
180 expected
181 (error_list_to_string [e])
182 "Errors.try_ should call the handler with first encountered error";
183 true
185 let test_merge () =
186 let (errors1, ()) =
187 Errors.do_ (fun () ->
188 error_in "A";
189 error_in "B";
192 let (errors2, ()) =
193 Errors.do_ (fun () ->
194 error_in "C";
195 error_in "D";
198 let errors = Errors.merge errors1 errors2 in
199 let expected =
200 expect_error_in "B"
201 ^ expect_error_in "A"
202 ^ expect_error_in "C"
203 ^ expect_error_in "D"
205 Asserter.String_asserter.assert_equals
206 expected
207 (Errors.get_error_list errors |> error_list_to_string)
208 "Errors.merge behaves like List.rev_append";
210 let errors = Errors.merge errors1 Errors.empty in
211 let expected = expect_error_in "B" ^ expect_error_in "A" in
212 Asserter.String_asserter.assert_equals
213 expected
214 (Errors.get_error_list errors |> error_list_to_string)
215 "Errors.merge behaves like List.rev_append";
217 let errors = Errors.merge Errors.empty errors2 in
218 let expected = expect_error_in "C" ^ expect_error_in "D" in
219 Asserter.String_asserter.assert_equals
220 expected
221 (Errors.get_error_list errors |> error_list_to_string)
222 "Errors.merge behaves like List.rev_append";
223 true
225 let test_from_error_list () =
226 let (errors, ()) =
227 Errors.do_ (fun () ->
228 error_in "A";
229 error_in "B";
232 let errors = Errors.get_error_list errors in
233 let expected = error_list_to_string errors in
234 let errors = Errors.(errors |> from_error_list |> get_error_list) in
235 Asserter.String_asserter.assert_equals
236 expected
237 (errors |> error_list_to_string)
238 "get_error_list(from_error_list(x)) == x";
239 Asserter.Bool_asserter.assert_equals
240 true
241 Errors.([] |> from_error_list |> is_empty)
242 "is_empty(from_error_list([])) == true";
243 true
245 let test_phases () =
246 let a_path = create_path "A" in
247 let (errors, ()) =
248 Errors.do_ (fun () ->
249 Errors.run_in_context a_path Errors.Parsing (fun () ->
250 Errors.parsing_error (Pos.make_from a_path, ""));
251 Errors.run_in_context a_path Errors.Typing (fun () ->
252 Errors.typing_error (Pos.make_from a_path) "");
255 let expected =
256 "File \"/A\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
257 ^ "File \"/A\", line 0, characters 0-0:\n (Typing[4116])\n\n"
259 Asserter.String_asserter.assert_equals
260 expected
261 (Errors.get_error_list errors |> error_list_to_string)
262 "Errors from earlier phase should come first";
263 true
265 let test_incremental_update () =
266 let a_path = create_path "A" in
267 let b_path = create_path "B" in
268 let (foo_error_a, ()) =
269 Errors.do_with_context a_path Errors.Parsing (fun () ->
270 error_in "foo1";
271 error_in "foo2";
274 let (bar_error_a, ()) =
275 Errors.do_with_context a_path Errors.Parsing (fun () ->
276 error_in "bar1";
277 error_in "bar2";
280 let (baz_error_b, ()) =
281 Errors.do_with_context b_path Errors.Parsing (fun () ->
282 error_in "baz1";
283 error_in "baz2";
286 let errors =
287 Errors.incremental_update_set
288 ~old:foo_error_a
289 ~new_:bar_error_a
290 ~rechecked:(Relative_path.Set.singleton a_path)
291 Errors.Parsing
293 let expected =
294 "File \"/bar2\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
295 ^ "File \"/bar1\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
297 Asserter.String_asserter.assert_equals
298 expected
299 (Errors.get_error_list errors |> error_list_to_string)
300 "Incremental update should overwrite foo error with bar.";
302 let errors =
303 Errors.incremental_update_set
304 ~old:foo_error_a
305 ~new_:baz_error_b
306 ~rechecked:(Relative_path.Set.singleton b_path)
307 Errors.Parsing
309 let expected =
310 "File \"/foo1\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
311 ^ "File \"/foo2\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
312 ^ "File \"/baz2\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
313 ^ "File \"/baz1\", line 0, characters 0-0:\n (Parsing[1002])\n\n"
315 Asserter.String_asserter.assert_equals
316 expected
317 (Errors.get_error_list errors |> error_list_to_string)
318 "Incremental update should add baz error and leave foo error unchanged";
320 let errors =
321 Errors.incremental_update_set
322 ~old:foo_error_a
323 ~new_:Errors.empty
324 ~rechecked:(Relative_path.Set.singleton a_path)
325 Errors.Parsing
327 Asserter.Bool_asserter.assert_equals
328 true
329 (Errors.is_empty errors)
330 "Incremental update should clear errors if a rechecked file has no errors";
331 true
333 let test_merge_into_current () =
334 let (errors1, ()) =
335 Errors.do_ (fun () ->
336 error_in "A";
337 error_in "B";
338 error_in "C";
339 error_in "D";
340 error_in "E";
341 error_in "F";
344 let expected =
345 expect_error_in "A"
346 ^ expect_error_in "B"
347 ^ expect_error_in "C"
348 ^ expect_error_in "D"
349 ^ expect_error_in "E"
350 ^ expect_error_in "F"
352 let error_message =
353 "merge_into_current should behave as if the code that "
354 ^ "generated errors was inlined at the callsite."
356 Asserter.String_asserter.assert_equals
357 expected
358 (Errors.get_error_list errors1 |> error_list_to_string)
359 error_message;
361 let (sub_errors, ()) =
362 Errors.do_ (fun () ->
363 error_in "C";
364 error_in "D";
367 let (errors2, ()) =
368 Errors.do_ (fun () ->
369 Errors.merge_into_current sub_errors;
372 let expected2 = expect_error_in "C" ^ expect_error_in "D" in
373 Asserter.String_asserter.assert_equals
374 expected2
375 (Errors.get_error_list errors2 |> error_list_to_string)
376 error_message;
378 let (errors, ()) =
379 Errors.do_ (fun () ->
380 error_in "A";
381 error_in "B";
382 Errors.merge_into_current sub_errors;
383 error_in "E";
384 error_in "F";
387 Asserter.String_asserter.assert_equals
388 expected
389 (Errors.get_error_list errors |> error_list_to_string)
390 error_message;
391 true
393 (* Errors.merge is called on very critical paths in Parsing_service,
394 * Decl_redecl_service, and Typing_check_service to merge partial results from
395 * workers. If it's too slow, it delays scheduling of more jobs, and hurts
396 * parallelism rate. All those callsites pass the second argument as the
397 * accumulator, so the runtime needs to be proportional to the size of first
398 * argument. *)
399 let test_performance () =
400 let n = 1000000 in
401 let rec aux acc = function
402 | 0 -> acc
403 | n ->
404 let path = string_of_int n ^ ".php" in
405 let (errors, ()) =
406 Errors.(do_with_context (create_path path) Typing) (fun () ->
407 error_in path)
409 (* note argument order: small first, big second *)
410 aux (Errors.merge errors acc) (n - 1)
412 let errors = aux Errors.empty n in
413 List.length (Errors.get_error_list errors) == n
415 let tests =
417 ("test", test_do);
418 ("test_get_sorted_error_list", test_get_sorted_error_list);
419 ("test_try", test_try);
420 ("test_merge", test_merge);
421 ("test_from_error_list", test_from_error_list);
422 ("test_phases", test_phases);
423 (* TODO T44055462 please amend test to maintain new invariants of error API
424 "test_incremental_update", test_incremental_update; *)
425 ("test_merge_into_current", test_merge_into_current);
426 ("test_performance", test_performance);
429 let () =
430 Relative_path.(set_path_prefix Root (Path.make "/"));
431 Unit_test.run_all tests