Skip to content

Commit 8b97c21

Browse files
committed
Improve reliability of scheduler tests
We got a failure in Jenkins: ``` ASSERT one_shot_success -------------------------------------------------------------------------------- [failure] Error one_shot_success: expecting true, got false. ``` The test schedules a job for 1 second in the future and then checks that it took `(0.99, 2.01) s` to run it. But the scheduler, and indeed Thread.delay cannot make such guarantees. It can only ensure that the job is scheduled *at least* 1 s in the future. If the system is busy it can take 2s, 30s, 10m, etc. the kernel provides no upper bound, this is not a real-time operating system. Replace the strict check with a timeout based one instead: wait up to a maximum allowed time, and then check how soon the callback actually got invoked (i.e. that it didn't run too early). We need the maximum allowed time because we don't want the test to be stuck forever, but we also want the test to stop as soon as the callback is executed, i.e. so it doesn't needlessly wait a minute on each test if the scheduler actually finished in 1s. Signed-off-by: Edwin Török <[email protected]>
1 parent b5ae2cf commit 8b97c21

File tree

1 file changed

+37
-45
lines changed

1 file changed

+37
-45
lines changed

lib_test/scheduler_test.ml

Lines changed: 37 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -26,60 +26,52 @@ let test_delay_cancel () =
2626
let elapsed = after -. before in
2727
assert_bool "elapsed_time1" (elapsed < 0.4)
2828

29+
let timed_wait_callback ~msg ?(time_min = 0.) ?(eps=0.1) ?(time_max = 60.) f =
30+
let rd, wr = Unix.pipe () in
31+
let finally () =
32+
Unix.close rd;
33+
Unix.close wr
34+
in
35+
Fun.protect ~finally (fun () ->
36+
let before = Unix.gettimeofday () in
37+
let after = ref None in
38+
let callback () =
39+
after := Some (Unix.gettimeofday ());
40+
let (_:int) = Unix.write_substring wr " " 0 1 in
41+
()
42+
in
43+
f callback;
44+
let ready = Thread.wait_timed_read rd time_max in
45+
match ready, !after with
46+
| true, None ->
47+
Alcotest.fail "pipe ready to read, but after is not set"
48+
| false, None ->
49+
Alcotest.fail (Printf.sprintf "%s: callback not invoked within %gs" msg time_max)
50+
| _, Some t ->
51+
let actual_minimum = min (t -. before) time_min in
52+
Alcotest.(check (float eps))
53+
(Printf.sprintf "%s: callback invoked earlier than expected" msg) time_min actual_minimum)
54+
55+
2956
(* Test the injection of a one-shot function at a time in the future *)
3057
let test_one_shot () =
31-
let after = ref None in
32-
let before = Unix.gettimeofday () in
33-
let _ = Scheduler.one_shot global_scheduler (Scheduler.Delta 1) "test_one_shot"
34-
(fun () -> after := Some (Unix.gettimeofday ())) in
35-
Thread.delay 2.0;
36-
let success =
37-
match !after with
38-
| Some x ->
39-
let elapsed = x -. before in
40-
elapsed > 0.99 && elapsed < 2.01
41-
| None ->
42-
false
43-
in
44-
assert_bool "one_shot_success" success
58+
timed_wait_callback ~msg:"one_shot_success" ~time_min:1.0 (fun callback ->
59+
ignore @@ Scheduler.one_shot global_scheduler (Scheduler.Delta 1) "test_one_shot" callback)
4560

4661
(* Test the injection of a one-shot function at an absolute time *)
4762
let test_one_shot_abs () =
48-
let after = ref None in
49-
let before = Unix.gettimeofday () in
50-
let now = Scheduler.now () in
51-
let _ = Scheduler.one_shot global_scheduler (Scheduler.Absolute (Int64.add 1L now)) "test_one_shot"
52-
(fun () -> after := Some (Unix.gettimeofday ())) in
53-
Thread.delay 2.0;
54-
let success =
55-
match !after with
56-
| Some x ->
57-
let elapsed = x -. before in
58-
elapsed > 0.99 && elapsed < 2.01
59-
| None ->
60-
false
61-
in
62-
assert_bool "one_shot_success" success
63+
timed_wait_callback ~msg:"one_shot_abs_success" ~time_min:1.0 (fun callback ->
64+
let now = Scheduler.now () in
65+
ignore @@ Scheduler.one_shot global_scheduler (Scheduler.Absolute (Int64.add 1L now)) "test_one_shot" callback)
6366

6467
(* Tests that the scheduler still works even after a failure occurs in
6568
the injected function *)
6669
let test_one_shot_failure () =
67-
let after = ref None in
68-
let before = Unix.gettimeofday () in
69-
let _ = Scheduler.one_shot global_scheduler (Scheduler.Delta 0) "test_one_shot"
70-
(fun () -> after := failwith "Error") in
71-
let _ = Scheduler.one_shot global_scheduler (Scheduler.Delta 1) "test_one_shot"
72-
(fun () -> after := Some (Unix.gettimeofday ())) in
73-
Thread.delay 2.0;
74-
let success =
75-
match !after with
76-
| Some x ->
77-
let elapsed = x -. before in
78-
elapsed > 0.99 && elapsed < 2.01
79-
| None ->
80-
false
81-
in
82-
assert_bool "one_shot_success" success
70+
timed_wait_callback ~msg:"one_show_failure" ~time_min:1.0 (fun callback ->
71+
let _ = Scheduler.one_shot global_scheduler (Scheduler.Delta 0) "test_one_shot"
72+
(fun () -> failwith "Error") in
73+
ignore @@ Scheduler.one_shot global_scheduler (Scheduler.Delta 1) "test_one_shot"
74+
callback)
8375

8476
(* Checks that one-shot functions can cancelled and are then not executed *)
8577
let test_one_shot_cancel () =

0 commit comments

Comments
 (0)