Skip to content

Commit 5d29136

Browse files
author
songmuhan
authored
docs: add semaphore example for running tests sequentially (#6038)
Signed-off-by: Muhan Song <songmuhan@stu.pku.edu.cn>
1 parent 52b29b3 commit 5d29136

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed

tokio/src/sync/semaphore.rs

+111
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,117 @@ use std::sync::Arc;
123123
/// # }
124124
/// ```
125125
///
126+
/// ## Prevent tests from running in parallel
127+
///
128+
/// By default, Rust runs tests in the same file in parallel. However, in some cases, running two tests in parallel may lead to problems.
129+
/// For example, this can happen when tests use the same database.
130+
///
131+
/// Consider the following scenario:
132+
/// 1. `test_insert`: Inserts a key-value pair into the database, then retrieves the value using the same key to verify the insertion.
133+
/// 2. `test_update`: Inserts a key, then updates the key to a new value and verifies that the value has been accurately updated.
134+
/// 3. `test_others`: A third test that doesn't modify the database state. It can run in parallel with the other tests.
135+
///
136+
/// In this example, `test_insert` and `test_update` need to run in sequence to work, but it doesn't matter which test runs first.
137+
/// We can leverage a semaphore with a single permit to address this challenge.
138+
///
139+
/// ```
140+
/// use tokio::sync::Semaphore;
141+
/// # use tokio::sync::Mutex;
142+
/// # use std::collections::BTreeMap;
143+
/// # struct Database {
144+
/// # map: Mutex<BTreeMap<String, i32>>,
145+
/// # }
146+
/// # impl Database {
147+
/// # pub const fn setup() -> Database {
148+
/// # Database {
149+
/// # map: Mutex::const_new(BTreeMap::new()),
150+
/// # }
151+
/// # }
152+
/// # pub async fn insert(&self, key: &str, value: i32) {
153+
/// # self.map.lock().await.insert(key.to_string(), value);
154+
/// # }
155+
/// # pub async fn update(&self, key: &str, value: i32) {
156+
/// # self.map.lock().await
157+
/// # .entry(key.to_string())
158+
/// # .and_modify(|origin| *origin = value);
159+
/// # }
160+
/// # pub async fn delete(&self, key: &str) {
161+
/// # self.map.lock().await.remove(key);
162+
/// # }
163+
/// # pub async fn get(&self, key: &str) -> i32 {
164+
/// # *self.map.lock().await.get(key).unwrap()
165+
/// # }
166+
/// # }
167+
///
168+
/// // Initialize a static semaphore with only one permit, which is used to
169+
/// // prevent test_insert and test_update from running in parallel.
170+
/// static PERMIT: Semaphore = Semaphore::const_new(1);
171+
///
172+
/// // Initialize the database that will be used by the subsequent tests.
173+
/// static DB: Database = Database::setup();
174+
///
175+
/// #[tokio::test]
176+
/// # async fn fake_test() {}
177+
/// async fn test_insert() {
178+
/// // Acquire permit before proceeding. Since the semaphore has only one permit,
179+
/// // the test will wait if the permit is already acquired by other tests.
180+
/// let permit = PERMIT.acquire().await.unwrap();
181+
///
182+
/// // Do the actual test stuff with database
183+
///
184+
/// // Insert a key-value pair to database
185+
/// let (key, value) = ("name", 0);
186+
/// DB.insert(key, value).await;
187+
///
188+
/// // Verify that the value has been inserted correctly.
189+
/// assert_eq!(DB.get(key).await, value);
190+
///
191+
/// // Undo the insertion, so the database is empty at the end of the test.
192+
/// DB.delete(key).await;
193+
///
194+
/// // Drop permit. This allows the other test to start running.
195+
/// drop(permit);
196+
/// }
197+
///
198+
/// #[tokio::test]
199+
/// # async fn fake_test() {}
200+
/// async fn test_update() {
201+
/// // Acquire permit before proceeding. Since the semaphore has only one permit,
202+
/// // the test will wait if the permit is already acquired by other tests.
203+
/// let permit = PERMIT.acquire().await.unwrap();
204+
///
205+
/// // Do the same insert.
206+
/// let (key, value) = ("name", 0);
207+
/// DB.insert(key, value).await;
208+
///
209+
/// // Update the existing value with a new one.
210+
/// let new_value = 1;
211+
/// DB.update(key, new_value).await;
212+
///
213+
/// // Verify that the value has been updated correctly.
214+
/// assert_eq!(DB.get(key).await, new_value);
215+
///
216+
/// // Undo any modificattion.
217+
/// DB.delete(key).await;
218+
///
219+
/// // Drop permit. This allows the other test to start running.
220+
/// drop(permit);
221+
/// }
222+
///
223+
/// #[tokio::test]
224+
/// # async fn fake_test() {}
225+
/// async fn test_others() {
226+
/// // This test can run in parallel with test_insert and test_update,
227+
/// // so it does not use PERMIT.
228+
/// }
229+
/// # #[tokio::main]
230+
/// # async fn main() {
231+
/// # test_insert().await;
232+
/// # test_update().await;
233+
/// # test_others().await;
234+
/// # }
235+
/// ```
236+
///
126237
/// ## Rate limiting using a token bucket
127238
///
128239
/// Many applications and systems have constraints on the rate at which certain

0 commit comments

Comments
 (0)