Skip to content

Commit dd10c1c

Browse files
committed
rate-limit: Write unit tests for bucket table
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
1 parent 68993df commit dd10c1c

File tree

4 files changed

+178
-6
lines changed

4 files changed

+178
-6
lines changed

ocaml/libs/rate-limit/test/dune

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(test
2-
(name test_token_bucket)
1+
(tests
2+
(names test_token_bucket test_bucket_table)
33
(package rate-limit)
4-
(libraries rate_limit alcotest qcheck-core qcheck-alcotest mtime mtime.clock.os fmt xapi-log threads.posix))
4+
(libraries rate_limit alcotest qcheck-core qcheck-alcotest mtime mtime.clock.os fmt xapi-log threads.posix))
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
open Rate_limit
2+
3+
let test_create () =
4+
let table = Bucket_table.create () in
5+
Alcotest.(check (option (float 0.0)))
6+
"Empty table returns None for peek" None
7+
(Bucket_table.peek table ~user_agent:"test")
8+
9+
let test_add_bucket () =
10+
let table = Bucket_table.create () in
11+
let success =
12+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:10.0
13+
~fill_rate:2.0
14+
in
15+
Alcotest.(check bool) "Adding valid bucket should succeed" true success ;
16+
Alcotest.(check (option (float 0.1)))
17+
"Peek should return burst_size" (Some 10.0)
18+
(Bucket_table.peek table ~user_agent:"agent1")
19+
20+
let test_add_bucket_invalid () =
21+
let table = Bucket_table.create () in
22+
let success =
23+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:10.0
24+
~fill_rate:0.0
25+
in
26+
Alcotest.(check bool)
27+
"Adding bucket with zero fill rate should fail" false success ;
28+
let success_neg =
29+
Bucket_table.add_bucket table ~user_agent:"agent2" ~burst_size:10.0
30+
~fill_rate:(-1.0)
31+
in
32+
Alcotest.(check bool)
33+
"Adding bucket with negative fill rate should fail" false success_neg
34+
35+
let test_delete_bucket () =
36+
let table = Bucket_table.create () in
37+
let _ =
38+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:10.0
39+
~fill_rate:2.0
40+
in
41+
Alcotest.(check (option (float 0.1)))
42+
"Bucket exists before delete" (Some 10.0)
43+
(Bucket_table.peek table ~user_agent:"agent1") ;
44+
Bucket_table.delete_bucket table ~user_agent:"agent1" ;
45+
Alcotest.(check (option (float 0.0)))
46+
"Bucket removed after delete" None
47+
(Bucket_table.peek table ~user_agent:"agent1")
48+
49+
let test_delete_nonexistent () =
50+
let table = Bucket_table.create () in
51+
Bucket_table.delete_bucket table ~user_agent:"nonexistent" ;
52+
Alcotest.(check pass) "Deleting nonexistent bucket should not raise" () ()
53+
54+
let test_try_consume () =
55+
let table = Bucket_table.create () in
56+
let _ =
57+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:10.0
58+
~fill_rate:2.0
59+
in
60+
let success = Bucket_table.try_consume table ~user_agent:"agent1" 3.0 in
61+
Alcotest.(check bool) "Consuming available tokens should succeed" true success ;
62+
Alcotest.(check (option (float 0.1)))
63+
"Tokens reduced after consume" (Some 7.0)
64+
(Bucket_table.peek table ~user_agent:"agent1")
65+
66+
let test_try_consume_insufficient () =
67+
let table = Bucket_table.create () in
68+
let _ =
69+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:5.0
70+
~fill_rate:1.0
71+
in
72+
let success = Bucket_table.try_consume table ~user_agent:"agent1" 10.0 in
73+
Alcotest.(check bool)
74+
"Consuming more than available should fail" false success ;
75+
Alcotest.(check (option (float 0.1)))
76+
"Tokens unchanged after failed consume" (Some 5.0)
77+
(Bucket_table.peek table ~user_agent:"agent1")
78+
79+
let test_try_consume_nonexistent () =
80+
let table = Bucket_table.create () in
81+
let success = Bucket_table.try_consume table ~user_agent:"nonexistent" 1.0 in
82+
Alcotest.(check bool)
83+
"Consuming from nonexistent bucket should fail" false success
84+
85+
let test_peek_nonexistent () =
86+
let table = Bucket_table.create () in
87+
Alcotest.(check (option (float 0.0)))
88+
"Peek nonexistent bucket returns None" None
89+
(Bucket_table.peek table ~user_agent:"nonexistent")
90+
91+
let test_multiple_agents () =
92+
let table = Bucket_table.create () in
93+
let _ =
94+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:10.0
95+
~fill_rate:2.0
96+
in
97+
let _ =
98+
Bucket_table.add_bucket table ~user_agent:"agent2" ~burst_size:20.0
99+
~fill_rate:5.0
100+
in
101+
let _ = Bucket_table.try_consume table ~user_agent:"agent1" 5.0 in
102+
Alcotest.(check (option (float 0.1)))
103+
"Agent1 tokens reduced" (Some 5.0)
104+
(Bucket_table.peek table ~user_agent:"agent1") ;
105+
Alcotest.(check (option (float 0.1)))
106+
"Agent2 tokens unchanged" (Some 20.0)
107+
(Bucket_table.peek table ~user_agent:"agent2")
108+
109+
let test_consume_and_block () =
110+
let table = Bucket_table.create () in
111+
let _ =
112+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:10.0
113+
~fill_rate:10.0
114+
in
115+
let _ = Bucket_table.try_consume table ~user_agent:"agent1" 10.0 in
116+
let start_time = Unix.gettimeofday () in
117+
Bucket_table.consume_and_block table ~user_agent:"agent1" 5.0 ;
118+
let elapsed = Unix.gettimeofday () -. start_time in
119+
Alcotest.(check bool)
120+
"consume_and_block should wait for tokens" true (elapsed >= 0.4)
121+
122+
let test_consume_and_block_nonexistent () =
123+
let table = Bucket_table.create () in
124+
Bucket_table.consume_and_block table ~user_agent:"nonexistent" 1.0 ;
125+
Alcotest.(check pass)
126+
"consume_and_block on nonexistent bucket should not block" () ()
127+
128+
let test_concurrent_access () =
129+
let table = Bucket_table.create () in
130+
let _ =
131+
Bucket_table.add_bucket table ~user_agent:"agent1" ~burst_size:100.0
132+
~fill_rate:0.01
133+
in
134+
let successful_consumes = ref 0 in
135+
let counter_mutex = Mutex.create () in
136+
let threads =
137+
Array.init 20 (fun _ ->
138+
Thread.create
139+
(fun () ->
140+
let success =
141+
Bucket_table.try_consume table ~user_agent:"agent1" 5.0
142+
in
143+
if success then (
144+
Mutex.lock counter_mutex ;
145+
incr successful_consumes ;
146+
Mutex.unlock counter_mutex
147+
)
148+
)
149+
()
150+
)
151+
in
152+
Array.iter Thread.join threads ;
153+
Alcotest.(check int)
154+
"Exactly 20 consumes should succeed" 20 !successful_consumes
155+
156+
let test =
157+
[
158+
("Create empty table", `Quick, test_create)
159+
; ("Add valid bucket", `Quick, test_add_bucket)
160+
; ("Add invalid bucket", `Quick, test_add_bucket_invalid)
161+
; ("Delete bucket", `Quick, test_delete_bucket)
162+
; ("Delete nonexistent bucket", `Quick, test_delete_nonexistent)
163+
; ("Try consume", `Quick, test_try_consume)
164+
; ("Try consume insufficient", `Quick, test_try_consume_insufficient)
165+
; ("Try consume nonexistent", `Quick, test_try_consume_nonexistent)
166+
; ("Peek nonexistent", `Quick, test_peek_nonexistent)
167+
; ("Multiple agents", `Quick, test_multiple_agents)
168+
; ("Consume and block", `Slow, test_consume_and_block)
169+
; ("Consume and block nonexistent", `Quick, test_consume_and_block_nonexistent)
170+
; ("Concurrent access", `Quick, test_concurrent_access)
171+
]
172+
173+
let () = Alcotest.run "Bucket table library" [("Bucket table tests", test)]

ocaml/libs/rate-limit/test/test_bucket_table.mli

Whitespace-only changes.

ocaml/libs/rate-limit/test/test_token_bucket.ml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
open Thread
22
open Rate_limit
3-
open QCheck
43

54
let test_bad_fill_rate () =
65
let tb_zero = Token_bucket.create ~burst_size:1.0 ~fill_rate:0.0 in
@@ -374,8 +373,8 @@ let test_consume_quickcheck =
374373
gen_all
375374
in
376375

377-
Test.make ~name:"Consume operations maintain correct token count" ~count:100
378-
arb_all (fun (burst, fill, ops) -> property (burst, fill, ops)
376+
QCheck.Test.make ~name:"Consume operations maintain correct token count"
377+
~count:100 arb_all (fun (burst, fill, ops) -> property (burst, fill, ops)
379378
)
380379

381380
let test =

0 commit comments

Comments
 (0)