Skip to content

Commit 4f45329

Browse files
authored
tests: Add tests to cover the context feature (#130)
* Fix async example in README * Fix type checker error in __init__ * Remove unused T * Add client library tests * More detailed client tests * Improve worker version check and add tests for it * Fix clippy error * Add more tests * Add context tests
1 parent ef63345 commit 4f45329

11 files changed

Lines changed: 383 additions & 51 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ FluxQueue supports async functions too. Just define an async function and use th
7878

7979
```python
8080
@fluxqueue.task()
81-
async def send_email(data: dict):
81+
async def send_email(to_email: str, subject: str, body: str):
8282
async with email_context() as email_client:
8383
message = EmailMessage()
8484
message["From"] = "test@example.com"

crates/fluxqueue-worker/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod logger;
22
mod redis_client;
33
mod task;
4+
mod version_check;
45
mod worker;
56

67
pub use worker::*;
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
fn normalize(version: &str) -> String {
2+
version
3+
.replace("-rc-", "rc")
4+
.replace("-beta-", "b")
5+
.replace("-alpha-", "a")
6+
}
7+
8+
fn parse_part(part: &str) -> (u32, i32, u32) {
9+
let mut digits = String::new();
10+
let mut suffix = String::new();
11+
12+
for c in part.chars() {
13+
if c.is_ascii_digit() && suffix.is_empty() {
14+
digits.push(c);
15+
} else {
16+
suffix.push(c);
17+
}
18+
}
19+
20+
let number = digits.parse().unwrap_or(0);
21+
22+
let (ptype, pnum) = if let Some(stripped) = suffix.strip_prefix("rc") {
23+
(2, stripped.parse().unwrap_or(0))
24+
} else if let Some(stripped) = suffix.strip_prefix('b') {
25+
(1, stripped.parse().unwrap_or(0))
26+
} else if let Some(stripped) = suffix.strip_prefix('a') {
27+
(0, stripped.parse().unwrap_or(0))
28+
} else {
29+
(3, 0) // stable
30+
};
31+
32+
(number, ptype, pnum)
33+
}
34+
35+
pub fn compare_versions(v1: &str, v2: &str) -> i8 {
36+
let v1 = normalize(v1);
37+
let v2 = normalize(v2);
38+
39+
let parts1: Vec<_> = v1.split('.').collect();
40+
let parts2: Vec<_> = v2.split('.').collect();
41+
42+
let len = parts1.len().max(parts2.len());
43+
44+
for i in 0..len {
45+
let p1 = parts1.get(i).unwrap_or(&"0");
46+
let p2 = parts2.get(i).unwrap_or(&"0");
47+
48+
let (n1, t1, r1) = parse_part(p1);
49+
let (n2, t2, r2) = parse_part(p2);
50+
51+
if n1 != n2 {
52+
return if n1 > n2 { 1 } else { -1 };
53+
}
54+
55+
if t1 != t2 {
56+
return if t1 > t2 { 1 } else { -1 };
57+
}
58+
59+
if r1 != r2 {
60+
return if r1 > r2 { 1 } else { -1 };
61+
}
62+
}
63+
64+
0
65+
}
66+
67+
#[cfg(test)]
68+
mod tests {
69+
use super::*;
70+
71+
#[test]
72+
fn test_compare_versions() {
73+
let worker_version = "0.3.1";
74+
let client_lib_version = "0.1.2";
75+
76+
assert!(
77+
compare_versions(worker_version, client_lib_version) == 1,
78+
"left version is greater than the right one"
79+
);
80+
81+
assert!(
82+
compare_versions(client_lib_version, worker_version) == -1,
83+
"left version is less than the right one"
84+
);
85+
86+
assert!(
87+
compare_versions("0.2.0", "0.2.0") == 0,
88+
"both are equal versions"
89+
);
90+
91+
assert!(
92+
compare_versions("1.2.0", "1.2") == 0,
93+
"both are equal versions"
94+
);
95+
96+
assert!(
97+
compare_versions("1.2.3", "1.2.3a1") == 1,
98+
"alpha version is less than the latest"
99+
);
100+
101+
assert!(
102+
compare_versions("1.2.3", "1.2.3b1") == 1,
103+
"beta version is less than the latest"
104+
);
105+
106+
assert!(
107+
compare_versions("1.2.3", "1.2.3rc1") == 1,
108+
"rc version is less than the latest"
109+
);
110+
111+
assert!(
112+
compare_versions("1.2.3-rc-1", "1.2.3rc1") == 0,
113+
"both are equal"
114+
);
115+
116+
assert!(
117+
compare_versions("1.2.3-beta-1", "1.2.3b1") == 0,
118+
"both are equal"
119+
);
120+
121+
assert!(
122+
compare_versions("1.2.3-alpha-1", "1.2.3a1") == 0,
123+
"both are equal"
124+
);
125+
126+
assert!(
127+
compare_versions("1.2.3-rc-1", "1.2.3b1") == 1,
128+
"left is greater"
129+
);
130+
131+
assert!(compare_versions("1.2.3a1", "1.2.3b1") == -1, "alpha < beta");
132+
133+
assert!(compare_versions("1.2.3b1", "1.2.3rc1") == -1, "beta < rc");
134+
135+
assert!(compare_versions("1.2.3a1", "1.2.3rc1") == -1, "alpha < rc");
136+
137+
assert!(compare_versions("1.2.3rc2", "1.2.3rc1") == 1, "rc2 > rc1");
138+
139+
assert!(compare_versions("1.2.3b2", "1.2.3b10") == -1, "b2 < b10");
140+
141+
assert!(compare_versions("1.2.3a10", "1.2.3a2") == 1, "a10 > a2");
142+
143+
assert!(
144+
compare_versions("1.2.3.0", "1.2.3") == 0,
145+
"trailing zeros ignored"
146+
);
147+
148+
assert!(
149+
compare_versions("1.2.3.1", "1.2.3") == 1,
150+
"extra segment greater"
151+
);
152+
153+
assert!(
154+
compare_versions("1.2", "1.2.1") == -1,
155+
"missing segment treated as zero"
156+
);
157+
158+
assert!(
159+
compare_versions("1.2.3-rc-2", "1.2.3rc1") == 1,
160+
"rust rc2 > python rc1"
161+
);
162+
163+
assert!(
164+
compare_versions("1.2.3-beta-2", "1.2.3b1") == 1,
165+
"rust beta2 > python b1"
166+
);
167+
168+
assert!(
169+
compare_versions("1.2.3-alpha-2", "1.2.3a1") == 1,
170+
"rust alpha2 > python a1"
171+
);
172+
173+
assert!(
174+
compare_versions("1.2.3", "1.2.4a1") == -1,
175+
"next version prerelease is higher"
176+
);
177+
178+
assert!(
179+
compare_versions("1.2.4a1", "1.2.3") == 1,
180+
"next version prerelease is higher"
181+
);
182+
183+
assert!(
184+
compare_versions("1.2.3rc1", "1.2.3-rc-1") == 0,
185+
"symmetry equality"
186+
);
187+
}
188+
}

crates/fluxqueue-worker/src/worker.rs

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ fn check_client_library_version() -> Result<()> {
346346
Ok(version.to_string())
347347
})?;
348348

349-
let comparison = compare_versions(worker_version, &library_version);
349+
let comparison = crate::version_check::compare_versions(worker_version, &library_version);
350350

351351
if comparison == -1 {
352352
return Err(anyhow!(
@@ -367,25 +367,6 @@ fn check_client_library_version() -> Result<()> {
367367
Ok(())
368368
}
369369

370-
fn compare_versions(v1: &str, v2: &str) -> i8 {
371-
let mut parts1: Vec<u32> = v1.split('.').map(|p| p.parse().unwrap_or(0)).collect();
372-
let mut parts2: Vec<u32> = v2.split('.').map(|p| p.parse().unwrap_or(0)).collect();
373-
374-
let len = parts1.len().max(parts2.len());
375-
parts1.resize(len, 0);
376-
parts2.resize(len, 0);
377-
378-
for (a, b) in parts1.iter().zip(parts2.iter()) {
379-
if a > b {
380-
return 1;
381-
}
382-
if a < b {
383-
return -1;
384-
}
385-
}
386-
0
387-
}
388-
389370
#[cfg(test)]
390371
mod tests {
391372
use super::*;
@@ -464,6 +445,105 @@ mod tests {
464445
Ok(())
465446
}
466447

448+
#[tokio::test]
449+
async fn test_sync_task_with_context() -> Result<()> {
450+
let module_path_str = get_test_module_path("test_tasks_with_context.py");
451+
let task_registry = Arc::new(TaskRegistry::new(&module_path_str, "default")?);
452+
let dispatcher_pool = Arc::new(PythonDispatcher::new(task_registry.clone())?);
453+
454+
let task = task_registry.get_task(Arc::new("sync-func-with-context".to_string()));
455+
assert!(task.is_some());
456+
457+
if let Some(task_func) = task {
458+
let task = Task {
459+
id: "test-id".to_string(),
460+
name: "name".to_string(),
461+
args: vec![144],
462+
kwargs: vec![128],
463+
created_at: 0,
464+
retries: 0,
465+
max_retries: 3,
466+
};
467+
468+
let result = run_task(
469+
Arc::new("test".to_string()),
470+
dispatcher_pool.clone(),
471+
Arc::new(task),
472+
task_func,
473+
)
474+
.await;
475+
assert!(!result.is_err());
476+
}
477+
478+
Ok(())
479+
}
480+
481+
#[tokio::test]
482+
async fn test_async_task_with_context() -> Result<()> {
483+
let module_path_str = get_test_module_path("test_tasks_with_context.py");
484+
let task_registry = Arc::new(TaskRegistry::new(&module_path_str, "default")?);
485+
let dispatcher_pool = Arc::new(PythonDispatcher::new(task_registry.clone())?);
486+
487+
let task = task_registry.get_task(Arc::new("async-func-with-context".to_string()));
488+
assert!(task.is_some());
489+
490+
if let Some(task_func) = task {
491+
let task = Task {
492+
id: "test-id".to_string(),
493+
name: "name".to_string(),
494+
args: vec![144],
495+
kwargs: vec![128],
496+
created_at: 0,
497+
retries: 0,
498+
max_retries: 3,
499+
};
500+
501+
let result = run_task(
502+
Arc::new("test".to_string()),
503+
dispatcher_pool.clone(),
504+
Arc::new(task),
505+
task_func,
506+
)
507+
.await;
508+
assert!(!result.is_err());
509+
}
510+
511+
Ok(())
512+
}
513+
514+
#[tokio::test]
515+
async fn test_task_with_custom_context() -> Result<()> {
516+
let module_path_str = get_test_module_path("test_tasks_with_context.py");
517+
let task_registry = Arc::new(TaskRegistry::new(&module_path_str, "default")?);
518+
let dispatcher_pool = Arc::new(PythonDispatcher::new(task_registry.clone())?);
519+
520+
let task = task_registry.get_task(Arc::new("test-custom-context".to_string()));
521+
assert!(task.is_some());
522+
523+
if let Some(task_func) = task {
524+
let task = Task {
525+
id: "test-id".to_string(),
526+
name: "name".to_string(),
527+
args: vec![144],
528+
kwargs: vec![128],
529+
created_at: 0,
530+
retries: 0,
531+
max_retries: 3,
532+
};
533+
534+
let result = run_task(
535+
Arc::new("test".to_string()),
536+
dispatcher_pool.clone(),
537+
Arc::new(task),
538+
task_func,
539+
)
540+
.await;
541+
assert!(!result.is_err());
542+
}
543+
544+
Ok(())
545+
}
546+
467547
async fn enqueue_tasks(redis_url: &str) -> Result<()> {
468548
use deadpool_redis::{Config, Runtime};
469549
use fluxqueue_common::{
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from fluxqueue import FluxQueue
2+
3+
fluxqueue = FluxQueue()
Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
def task1():
2-
pass
3-
1+
from .main import fluxqueue
42

5-
task1.task_name = "task-1" # type: ignore
6-
task1.queue = "default" # type: ignore
73

8-
9-
def task2():
4+
@fluxqueue.task(name="task-1")
5+
def task_1():
106
pass
117

128

13-
task2.task_name = "task-1" # type: ignore
14-
task2.queue = "default" # type: ignore
9+
@fluxqueue.task(name="task-1")
10+
def task_2():
11+
pass

0 commit comments

Comments
 (0)