Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
change
sglang
Commits
41d33e47
"docs/zh_cn/get_started.md" did not exist on "caa2718f383494390697040e5bf73cd602c4b498"
Unverified
Commit
41d33e47
authored
Jul 19, 2025
by
Simo Lin
Committed by
GitHub
Jul 19, 2025
Browse files
[router] add ut for worker and errors (#8170)
parent
bfdd226f
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
789 additions
and
0 deletions
+789
-0
sgl-router/src/core/error.rs
sgl-router/src/core/error.rs
+179
-0
sgl-router/src/core/worker.rs
sgl-router/src/core/worker.rs
+610
-0
No files found.
sgl-router/src/core/error.rs
View file @
41d33e47
...
...
@@ -55,3 +55,182 @@ impl From<reqwest::Error> for WorkerError {
}
}
}
#[cfg(test)]
mod
tests
{
use
super
::
*
;
use
std
::
error
::
Error
;
#[test]
fn
test_health_check_failed_display
()
{
let
error
=
WorkerError
::
HealthCheckFailed
{
url
:
"http://worker1:8080"
.to_string
(),
reason
:
"Connection refused"
.to_string
(),
};
assert_eq!
(
error
.to_string
(),
"Health check failed for worker http://worker1:8080: Connection refused"
);
}
#[test]
fn
test_worker_not_found_display
()
{
let
error
=
WorkerError
::
WorkerNotFound
{
url
:
"http://worker2:8080"
.to_string
(),
};
assert_eq!
(
error
.to_string
(),
"Worker not found: http://worker2:8080"
);
}
#[test]
fn
test_invalid_configuration_display
()
{
let
error
=
WorkerError
::
InvalidConfiguration
{
message
:
"Missing port number"
.to_string
(),
};
assert_eq!
(
error
.to_string
(),
"Invalid worker configuration: Missing port number"
);
}
#[test]
fn
test_network_error_display
()
{
let
error
=
WorkerError
::
NetworkError
{
url
:
"http://worker3:8080"
.to_string
(),
error
:
"Timeout after 30s"
.to_string
(),
};
assert_eq!
(
error
.to_string
(),
"Network error for worker http://worker3:8080: Timeout after 30s"
);
}
#[test]
fn
test_worker_at_capacity_display
()
{
let
error
=
WorkerError
::
WorkerAtCapacity
{
url
:
"http://worker4:8080"
.to_string
(),
};
assert_eq!
(
error
.to_string
(),
"Worker at capacity: http://worker4:8080"
);
}
#[test]
fn
test_worker_error_implements_std_error
()
{
let
error
=
WorkerError
::
WorkerNotFound
{
url
:
"http://test"
.to_string
(),
};
// Verify it implements Error trait
let
_
:
&
dyn
Error
=
&
error
;
assert
!
(
error
.source
()
.is_none
());
}
#[test]
fn
test_error_send_sync
()
{
fn
assert_send_sync
<
T
:
Send
+
Sync
>
()
{}
assert_send_sync
::
<
WorkerError
>
();
}
#[test]
fn
test_worker_result_type_alias
()
{
// Test Ok variant
let
result
:
WorkerResult
<
i32
>
=
Ok
(
42
);
assert
!
(
result
.is_ok
());
assert_eq!
(
result
.unwrap
(),
42
);
// Test Err variant
let
error
=
WorkerError
::
WorkerNotFound
{
url
:
"test"
.to_string
(),
};
let
result
:
WorkerResult
<
i32
>
=
Err
(
error
);
assert
!
(
result
.is_err
());
}
#[test]
fn
test_empty_url_handling
()
{
// Test empty URLs in error variants
let
error1
=
WorkerError
::
HealthCheckFailed
{
url
:
""
.to_string
(),
reason
:
"No connection"
.to_string
(),
};
assert_eq!
(
error1
.to_string
(),
"Health check failed for worker : No connection"
);
let
error2
=
WorkerError
::
NetworkError
{
url
:
""
.to_string
(),
error
:
"DNS failure"
.to_string
(),
};
assert_eq!
(
error2
.to_string
(),
"Network error for worker : DNS failure"
);
let
error3
=
WorkerError
::
WorkerNotFound
{
url
:
""
.to_string
(),
};
assert_eq!
(
error3
.to_string
(),
"Worker not found: "
);
}
#[test]
fn
test_special_characters_in_messages
()
{
// Test with special characters
let
error
=
WorkerError
::
InvalidConfiguration
{
message
:
"Invalid JSON: {
\"
error
\"
:
\"
test
\"
}"
.to_string
(),
};
assert_eq!
(
error
.to_string
(),
"Invalid worker configuration: Invalid JSON: {
\"
error
\"
:
\"
test
\"
}"
);
// Test with unicode
let
error2
=
WorkerError
::
HealthCheckFailed
{
url
:
"http://测试:8080"
.to_string
(),
reason
:
"连接被拒绝"
.to_string
(),
};
assert_eq!
(
error2
.to_string
(),
"Health check failed for worker http://测试:8080: 连接被拒绝"
);
}
#[test]
fn
test_very_long_error_messages
()
{
let
long_message
=
"A"
.repeat
(
10000
);
let
error
=
WorkerError
::
InvalidConfiguration
{
message
:
long_message
.clone
(),
};
let
display
=
error
.to_string
();
assert
!
(
display
.contains
(
&
long_message
));
assert_eq!
(
display
.len
(),
"Invalid worker configuration: "
.len
()
+
long_message
.len
()
);
}
// Mock reqwest error for testing conversion
#[test]
fn
test_reqwest_error_conversion
()
{
// Test that NetworkError is the correct variant
let
network_error
=
WorkerError
::
NetworkError
{
url
:
"http://example.com"
.to_string
(),
error
:
"connection timeout"
.to_string
(),
};
match
network_error
{
WorkerError
::
NetworkError
{
url
,
error
}
=>
{
assert_eq!
(
url
,
"http://example.com"
);
assert_eq!
(
error
,
"connection timeout"
);
}
_
=>
panic!
(
"Expected NetworkError variant"
),
}
}
#[test]
fn
test_error_equality
()
{
// WorkerError doesn't implement PartialEq, but we can test that
// the same error construction produces the same display output
let
error1
=
WorkerError
::
WorkerNotFound
{
url
:
"http://test"
.to_string
(),
};
let
error2
=
WorkerError
::
WorkerNotFound
{
url
:
"http://test"
.to_string
(),
};
assert_eq!
(
error1
.to_string
(),
error2
.to_string
());
}
}
sgl-router/src/core/worker.rs
View file @
41d33e47
...
...
@@ -452,3 +452,613 @@ pub fn start_health_checker(
HealthChecker
{
handle
,
shutdown
}
}
#[cfg(test)]
mod
tests
{
use
super
::
*
;
use
std
::
sync
::
RwLock
;
use
std
::
time
::
Duration
;
use
tokio
::
time
::
timeout
;
// Test WorkerType
#[test]
fn
test_worker_type_display
()
{
assert_eq!
(
WorkerType
::
Regular
.to_string
(),
"Regular"
);
assert_eq!
(
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
8080
)
}
.to_string
(),
"Prefill(bootstrap:8080)"
);
assert_eq!
(
WorkerType
::
Prefill
{
bootstrap_port
:
None
}
.to_string
(),
"Prefill"
);
assert_eq!
(
WorkerType
::
Decode
.to_string
(),
"Decode"
);
}
#[test]
fn
test_worker_type_equality
()
{
assert_eq!
(
WorkerType
::
Regular
,
WorkerType
::
Regular
);
assert_ne!
(
WorkerType
::
Regular
,
WorkerType
::
Decode
);
assert_eq!
(
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
8080
)
},
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
8080
)
}
);
assert_ne!
(
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
8080
)
},
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
8081
)
}
);
}
#[test]
fn
test_worker_type_clone
()
{
let
original
=
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
8080
),
};
let
cloned
=
original
.clone
();
assert_eq!
(
original
,
cloned
);
}
// Test HealthConfig
#[test]
fn
test_health_config_default
()
{
let
config
=
HealthConfig
::
default
();
assert_eq!
(
config
.timeout_secs
,
5
);
assert_eq!
(
config
.check_interval_secs
,
30
);
assert_eq!
(
config
.endpoint
,
"/health"
);
}
#[test]
fn
test_health_config_custom
()
{
let
config
=
HealthConfig
{
timeout_secs
:
10
,
check_interval_secs
:
60
,
endpoint
:
"/healthz"
.to_string
(),
};
assert_eq!
(
config
.timeout_secs
,
10
);
assert_eq!
(
config
.check_interval_secs
,
60
);
assert_eq!
(
config
.endpoint
,
"/healthz"
);
}
// Test BasicWorker
#[test]
fn
test_basic_worker_creation
()
{
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
assert_eq!
(
worker
.url
(),
"http://test:8080"
);
assert_eq!
(
worker
.worker_type
(),
WorkerType
::
Regular
);
assert
!
(
worker
.is_healthy
());
assert_eq!
(
worker
.load
(),
0
);
assert_eq!
(
worker
.processed_requests
(),
0
);
}
#[test]
fn
test_worker_with_labels
()
{
let
mut
labels
=
std
::
collections
::
HashMap
::
new
();
labels
.insert
(
"env"
.to_string
(),
"prod"
.to_string
());
labels
.insert
(
"zone"
.to_string
(),
"us-west"
.to_string
());
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
)
.with_labels
(
labels
.clone
());
assert_eq!
(
worker
.metadata
()
.labels
,
labels
);
}
#[test]
fn
test_worker_with_health_config
()
{
let
custom_config
=
HealthConfig
{
timeout_secs
:
15
,
check_interval_secs
:
45
,
endpoint
:
"/custom-health"
.to_string
(),
};
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
)
.with_health_config
(
custom_config
.clone
());
assert_eq!
(
worker
.metadata
()
.health_config.timeout_secs
,
15
);
assert_eq!
(
worker
.metadata
()
.health_config.check_interval_secs
,
45
);
assert_eq!
(
worker
.metadata
()
.health_config.endpoint
,
"/custom-health"
);
}
// Test Worker trait implementation
#[test]
fn
test_worker_url
()
{
let
worker
=
BasicWorker
::
new
(
"http://worker1:8080"
.to_string
(),
WorkerType
::
Regular
);
assert_eq!
(
worker
.url
(),
"http://worker1:8080"
);
}
#[test]
fn
test_worker_type_getter
()
{
let
regular
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
assert_eq!
(
regular
.worker_type
(),
WorkerType
::
Regular
);
let
prefill
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
9090
),
},
);
assert_eq!
(
prefill
.worker_type
(),
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
9090
)
}
);
let
decode
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Decode
);
assert_eq!
(
decode
.worker_type
(),
WorkerType
::
Decode
);
}
#[test]
fn
test_health_status
()
{
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
// Initial state is healthy
assert
!
(
worker
.is_healthy
());
// Set unhealthy
worker
.set_healthy
(
false
);
assert
!
(
!
worker
.is_healthy
());
// Set healthy again
worker
.set_healthy
(
true
);
assert
!
(
worker
.is_healthy
());
}
#[test]
fn
test_load_counter_operations
()
{
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
// Initial load is 0
assert_eq!
(
worker
.load
(),
0
);
// Increment once
worker
.increment_load
();
assert_eq!
(
worker
.load
(),
1
);
// Increment twice more
worker
.increment_load
();
worker
.increment_load
();
assert_eq!
(
worker
.load
(),
3
);
// Decrement once
worker
.decrement_load
();
assert_eq!
(
worker
.load
(),
2
);
// Decrement to 0
worker
.decrement_load
();
worker
.decrement_load
();
assert_eq!
(
worker
.load
(),
0
);
// Decrement below 0 should stay at 0
worker
.decrement_load
();
assert_eq!
(
worker
.load
(),
0
);
}
#[test]
fn
test_processed_counter
()
{
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
// Initial count is 0
assert_eq!
(
worker
.processed_requests
(),
0
);
// Increment multiple times
for
i
in
1
..=
100
{
worker
.increment_processed
();
assert_eq!
(
worker
.processed_requests
(),
i
);
}
}
#[test]
fn
test_clone_worker
()
{
let
original
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
original
.increment_load
();
original
.increment_processed
();
original
.set_healthy
(
false
);
let
cloned
=
original
.clone_worker
();
// Verify cloned worker has same URL and type
assert_eq!
(
cloned
.url
(),
original
.url
());
assert_eq!
(
cloned
.worker_type
(),
original
.worker_type
());
// Load counters should be independent (cloned shares the Arc)
assert_eq!
(
cloned
.load
(),
original
.load
());
// Modify original and verify clone is affected (shared state)
original
.increment_load
();
assert_eq!
(
cloned
.load
(),
original
.load
());
}
// Test concurrent operations
#[tokio::test]
async
fn
test_concurrent_load_increments
()
{
let
worker
=
Arc
::
new
(
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
,
));
let
mut
handles
=
vec!
[];
// Spawn 100 tasks incrementing load
for
_
in
0
..
100
{
let
worker_clone
=
Arc
::
clone
(
&
worker
);
let
handle
=
tokio
::
spawn
(
async
move
{
worker_clone
.increment_load
();
});
handles
.push
(
handle
);
}
// Wait for all tasks
for
handle
in
handles
{
handle
.await
.unwrap
();
}
// Final count should be 100
assert_eq!
(
worker
.load
(),
100
);
}
#[tokio::test]
async
fn
test_concurrent_load_decrements
()
{
let
worker
=
Arc
::
new
(
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
,
));
// Set initial load to 100
for
_
in
0
..
100
{
worker
.increment_load
();
}
assert_eq!
(
worker
.load
(),
100
);
let
mut
handles
=
vec!
[];
// Spawn 100 tasks decrementing load
for
_
in
0
..
100
{
let
worker_clone
=
Arc
::
clone
(
&
worker
);
let
handle
=
tokio
::
spawn
(
async
move
{
worker_clone
.decrement_load
();
});
handles
.push
(
handle
);
}
// Wait for all tasks
for
handle
in
handles
{
handle
.await
.unwrap
();
}
// Final count should be 0
assert_eq!
(
worker
.load
(),
0
);
}
#[tokio::test]
async
fn
test_concurrent_health_updates
()
{
let
worker
=
Arc
::
new
(
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
,
));
let
mut
handles
=
vec!
[];
// Spawn threads randomly setting health status
for
i
in
0
..
100
{
let
worker_clone
=
Arc
::
clone
(
&
worker
);
let
handle
=
tokio
::
spawn
(
async
move
{
worker_clone
.set_healthy
(
i
%
2
==
0
);
tokio
::
time
::
sleep
(
Duration
::
from_micros
(
10
))
.await
;
});
handles
.push
(
handle
);
}
// Wait for all tasks
for
handle
in
handles
{
handle
.await
.unwrap
();
}
// Final state should be deterministic (last write wins)
// We can't predict the exact final state due to scheduling,
// but we can verify no data corruption occurred
let
final_health
=
worker
.is_healthy
();
assert
!
(
final_health
==
true
||
final_health
==
false
);
}
// Test WorkerFactory
#[test]
fn
test_create_regular_worker
()
{
let
worker
=
WorkerFactory
::
create_regular
(
"http://regular:8080"
.to_string
());
assert_eq!
(
worker
.url
(),
"http://regular:8080"
);
assert_eq!
(
worker
.worker_type
(),
WorkerType
::
Regular
);
}
#[test]
fn
test_create_prefill_worker
()
{
// With bootstrap port
let
worker1
=
WorkerFactory
::
create_prefill
(
"http://prefill:8080"
.to_string
(),
Some
(
9090
));
assert_eq!
(
worker1
.url
(),
"http://prefill:8080"
);
assert_eq!
(
worker1
.worker_type
(),
WorkerType
::
Prefill
{
bootstrap_port
:
Some
(
9090
)
}
);
// Without bootstrap port
let
worker2
=
WorkerFactory
::
create_prefill
(
"http://prefill:8080"
.to_string
(),
None
);
assert_eq!
(
worker2
.worker_type
(),
WorkerType
::
Prefill
{
bootstrap_port
:
None
}
);
}
#[test]
fn
test_create_decode_worker
()
{
let
worker
=
WorkerFactory
::
create_decode
(
"http://decode:8080"
.to_string
());
assert_eq!
(
worker
.url
(),
"http://decode:8080"
);
assert_eq!
(
worker
.worker_type
(),
WorkerType
::
Decode
);
}
#[test]
fn
test_create_from_urls
()
{
let
regular_urls
=
vec!
[
"http://regular1:8080"
.to_string
(),
"http://regular2:8080"
.to_string
(),
];
let
prefill_urls
=
vec!
[
(
"http://prefill1:8080"
.to_string
(),
Some
(
9090
)),
(
"http://prefill2:8080"
.to_string
(),
None
),
];
let
decode_urls
=
vec!
[
"http://decode1:8080"
.to_string
(),
"http://decode2:8080"
.to_string
(),
];
let
(
regular
,
prefill
,
decode
)
=
WorkerFactory
::
create_from_urls
(
regular_urls
,
prefill_urls
,
decode_urls
);
assert_eq!
(
regular
.len
(),
2
);
assert_eq!
(
prefill
.len
(),
2
);
assert_eq!
(
decode
.len
(),
2
);
assert_eq!
(
regular
[
0
]
.url
(),
"http://regular1:8080"
);
assert_eq!
(
prefill
[
0
]
.url
(),
"http://prefill1:8080"
);
assert_eq!
(
decode
[
0
]
.url
(),
"http://decode1:8080"
);
}
// Test WorkerCollection trait
#[test]
fn
test_healthy_workers_filter
()
{
let
workers
:
Vec
<
Box
<
dyn
Worker
>>
=
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w2:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w3:8080"
.to_string
()),
];
// Set some workers unhealthy
workers
[
0
]
.set_healthy
(
false
);
workers
[
2
]
.set_healthy
(
false
);
let
healthy
=
workers
.healthy_workers
();
assert_eq!
(
healthy
.len
(),
1
);
assert_eq!
(
healthy
[
0
]
.url
(),
"http://w2:8080"
);
}
#[test]
fn
test_total_load_calculation
()
{
let
workers
:
Vec
<
Box
<
dyn
Worker
>>
=
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w2:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w3:8080"
.to_string
()),
];
// Set different loads
workers
[
0
]
.increment_load
();
workers
[
0
]
.increment_load
();
// load = 2
workers
[
1
]
.increment_load
();
workers
[
1
]
.increment_load
();
workers
[
1
]
.increment_load
();
// load = 3
workers
[
2
]
.increment_load
();
// load = 1
assert_eq!
(
workers
.total_load
(),
6
);
}
#[test]
fn
test_find_worker
()
{
let
workers
:
Vec
<
Box
<
dyn
Worker
>>
=
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w2:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w3:8080"
.to_string
()),
];
// Found case
let
found
=
workers
.find_worker
(
"http://w2:8080"
);
assert
!
(
found
.is_some
());
assert_eq!
(
found
.unwrap
()
.url
(),
"http://w2:8080"
);
// Not found case
let
not_found
=
workers
.find_worker
(
"http://w4:8080"
);
assert
!
(
not_found
.is_none
());
}
#[test]
fn
test_find_worker_mut
()
{
let
mut
workers
:
Vec
<
Box
<
dyn
Worker
>>
=
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w2:8080"
.to_string
()),
];
// Find and modify
if
let
Some
(
worker
)
=
workers
.find_worker_mut
(
"http://w1:8080"
)
{
worker
.set_healthy
(
false
);
}
// Verify modification
assert
!
(
!
workers
[
0
]
.is_healthy
());
assert
!
(
workers
[
1
]
.is_healthy
());
}
// Test WorkerLoadGuard
#[test]
fn
test_load_guard_single_worker
()
{
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
assert_eq!
(
worker
.load
(),
0
);
{
let
_
guard
=
WorkerLoadGuard
::
new
(
&
worker
);
assert_eq!
(
worker
.load
(),
1
);
}
// Guard dropped, load decremented
assert_eq!
(
worker
.load
(),
0
);
}
#[test]
fn
test_load_guard_multiple_workers
()
{
let
workers
:
Vec
<
Box
<
dyn
Worker
>>
=
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w2:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w3:8080"
.to_string
()),
];
let
worker_refs
:
Vec
<&
dyn
Worker
>
=
workers
.iter
()
.map
(|
w
|
w
.as_ref
())
.collect
();
{
let
_
guard
=
WorkerLoadGuard
::
new_multi
(
worker_refs
);
// All loads incremented
assert_eq!
(
workers
[
0
]
.load
(),
1
);
assert_eq!
(
workers
[
1
]
.load
(),
1
);
assert_eq!
(
workers
[
2
]
.load
(),
1
);
}
// All loads decremented
assert_eq!
(
workers
[
0
]
.load
(),
0
);
assert_eq!
(
workers
[
1
]
.load
(),
0
);
assert_eq!
(
workers
[
2
]
.load
(),
0
);
}
#[test]
fn
test_load_guard_panic_safety
()
{
let
worker
=
Arc
::
new
(
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
,
));
assert_eq!
(
worker
.load
(),
0
);
// Clone for use inside catch_unwind
let
worker_clone
=
Arc
::
clone
(
&
worker
);
// This will panic, but the guard should still clean up
let
result
=
std
::
panic
::
catch_unwind
(||
{
let
_
guard
=
WorkerLoadGuard
::
new
(
worker_clone
.as_ref
());
assert_eq!
(
worker_clone
.load
(),
1
);
panic!
(
"Test panic"
);
});
// Verify panic occurred
assert
!
(
result
.is_err
());
// Load should be decremented even after panic
assert_eq!
(
worker
.load
(),
0
);
}
// Test helper functions
#[test]
fn
test_urls_to_workers
()
{
let
urls
=
vec!
[
"http://w1:8080"
.to_string
(),
"http://w2:8080"
.to_string
()];
let
workers
=
urls_to_workers
(
urls
);
assert_eq!
(
workers
.len
(),
2
);
assert_eq!
(
workers
[
0
]
.url
(),
"http://w1:8080"
);
assert_eq!
(
workers
[
1
]
.url
(),
"http://w2:8080"
);
assert_eq!
(
workers
[
0
]
.worker_type
(),
WorkerType
::
Regular
);
}
#[test]
fn
test_workers_to_urls
()
{
let
workers
:
Vec
<
Box
<
dyn
Worker
>>
=
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
()),
WorkerFactory
::
create_regular
(
"http://w2:8080"
.to_string
()),
];
let
urls
=
workers_to_urls
(
&
workers
);
assert_eq!
(
urls
,
vec!
[
"http://w1:8080"
,
"http://w2:8080"
]);
}
// Test synchronous health check wrapper
#[test]
fn
test_check_health_sync_wrapper
()
{
// We can't easily test the actual HTTP call without mocking,
// but we can verify the sync wrapper works
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
// This will fail because there's no server at this URL,
// but it tests that the sync wrapper doesn't panic
let
result
=
worker
.check_health
();
assert
!
(
result
.is_err
());
}
// Test HealthChecker background task
#[tokio::test]
async
fn
test_health_checker_startup
()
{
let
workers
=
Arc
::
new
(
RwLock
::
new
(
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
(),
)]));
let
checker
=
start_health_checker
(
workers
.clone
(),
60
);
// Verify it starts without panic
tokio
::
time
::
sleep
(
Duration
::
from_millis
(
100
))
.await
;
// Shutdown
checker
.shutdown
()
.await
;
}
#[tokio::test]
async
fn
test_health_checker_shutdown
()
{
let
workers
=
Arc
::
new
(
RwLock
::
new
(
vec!
[
WorkerFactory
::
create_regular
(
"http://w1:8080"
.to_string
(),
)]));
let
checker
=
start_health_checker
(
workers
.clone
(),
60
);
// Shutdown should complete quickly
let
shutdown_result
=
timeout
(
Duration
::
from_secs
(
1
),
checker
.shutdown
())
.await
;
assert
!
(
shutdown_result
.is_ok
());
}
// Performance test for load counter
#[test]
fn
test_load_counter_performance
()
{
use
std
::
time
::
Instant
;
let
worker
=
BasicWorker
::
new
(
"http://test:8080"
.to_string
(),
WorkerType
::
Regular
);
let
iterations
=
1_000_000
;
let
start
=
Instant
::
now
();
for
_
in
0
..
iterations
{
worker
.increment_load
();
}
let
duration
=
start
.elapsed
();
let
ops_per_sec
=
iterations
as
f64
/
duration
.as_secs_f64
();
println!
(
"Load counter operations per second: {:.0}"
,
ops_per_sec
);
// Should be well over 1M ops/sec
assert
!
(
ops_per_sec
>
1_000_000.0
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment