@@ -349,3 +349,190 @@ impl WorkerSelector for DefaultWorkerSelector {
349349 } )
350350 }
351351}
352+
353+ #[ cfg( test) ]
354+ mod tests {
355+ use super :: * ;
356+
357+ // Helper to create a worker endpoint
358+ fn create_endpoint (
359+ worker_id : i64 ,
360+ gpu_cache_usage_perc : f32 ,
361+ num_requests_waiting : u64 ,
362+ ) -> Endpoint {
363+ Endpoint {
364+ name : format ! ( "worker-{}" , worker_id) ,
365+ subject : format ! ( "worker-subject-{:x}" , worker_id) ,
366+ data : ForwardPassMetrics {
367+ gpu_cache_usage_perc,
368+ num_requests_waiting,
369+ // Other fields can be default initialized for this test
370+ ..Default :: default ( )
371+ } ,
372+ }
373+ }
374+
375+ // Helper to create ProcessedEndpoints
376+ struct WorkerInfo {
377+ id : i64 ,
378+ usage : f32 ,
379+ waiting : u64 ,
380+ }
381+ fn create_workers ( workers : Vec < WorkerInfo > ) -> ProcessedEndpoints {
382+ let mut endpoints = HashMap :: new ( ) ;
383+ for worker in workers {
384+ endpoints. insert (
385+ worker. id ,
386+ create_endpoint ( worker. id , worker. usage , worker. waiting ) ,
387+ ) ;
388+ }
389+ ProcessedEndpoints {
390+ endpoints,
391+ load_avg : 0.0 ,
392+ load_std : 0.0 ,
393+ }
394+ }
395+
396+ // Helper to create a scheduling request
397+ struct WorkerOverlap {
398+ worker_id : i64 ,
399+ overlap_blocks : u32 ,
400+ }
401+ fn create_request ( overlaps : Vec < WorkerOverlap > , isl_tokens : usize ) -> SchedulingRequest {
402+ SchedulingRequest {
403+ isl_tokens,
404+ overlap : OverlapScores {
405+ scores : overlaps
406+ . into_iter ( )
407+ . map ( |wo| ( wo. worker_id , wo. overlap_blocks ) )
408+ . collect ( ) ,
409+ frequencies : vec ! [ ] ,
410+ } ,
411+ resp_tx : tokio:: sync:: oneshot:: channel ( ) . 0 ,
412+ }
413+ }
414+
415+ #[ test]
416+ fn test_select_worker_basic ( ) {
417+ // Setup workers
418+ let workers = create_workers ( vec ! [
419+ WorkerInfo {
420+ id: 1 ,
421+ usage: 0.50 ,
422+ waiting: 1 ,
423+ } ,
424+ WorkerInfo {
425+ id: 2 ,
426+ usage: 0.80 ,
427+ waiting: 0 ,
428+ } ,
429+ ] ) ;
430+
431+ // Setup request: 100 tokens, block_size=20 (5 blocks)
432+ let request = create_request (
433+ vec ! [
434+ WorkerOverlap {
435+ worker_id: 1 ,
436+ overlap_blocks: 3 ,
437+ } ,
438+ WorkerOverlap {
439+ worker_id: 2 ,
440+ overlap_blocks: 4 ,
441+ } ,
442+ ] ,
443+ 100 ,
444+ ) ;
445+ let selector = DefaultWorkerSelector :: new ( None ) ;
446+ let block_size = 20 ;
447+
448+ // Execute selection
449+ let result = selector
450+ . select_worker ( & workers, & request, block_size)
451+ . expect ( "Should select a worker" ) ;
452+ // Worker 2 should win because:
453+ // Worker1: 2.0 * 0.600 - 1.0 * 0.500 - 1.0 * 1.000 = -0.3
454+ // Worker2: 2.0 * 0.800 - 1.0 * 0.800 - 1.0 * 0.000 = 0.8
455+ assert_eq ! ( result. worker_id, 2 ) ;
456+ assert_eq ! ( result. required_blocks, 5 ) ; // 100 tokens / 20 block_size
457+ assert_eq ! ( result. overlap_blocks, 4 ) ;
458+ }
459+
460+ #[ test]
461+ fn test_no_endpoints ( ) {
462+ let workers = create_workers ( vec ! [ ] ) ;
463+ let request = create_request ( vec ! [ ] , 100 ) ;
464+ let selector = DefaultWorkerSelector :: new ( None ) ;
465+ let block_size = 20 ;
466+
467+ match selector. select_worker ( & workers, & request, block_size) {
468+ Err ( KvSchedulerError :: NoEndpoints ) => { } // Expected
469+ _ => panic ! ( "Should return NoEndpoints error" ) ,
470+ }
471+ }
472+
473+ #[ test]
474+ fn test_no_overlap_scores ( ) {
475+ // Workers exist but request has no overlap scores
476+ let workers = create_workers ( vec ! [ WorkerInfo {
477+ id: 1 ,
478+ usage: 0.50 ,
479+ waiting: 1 ,
480+ } ] ) ;
481+ let request = create_request ( vec ! [ ] , 100 ) ; // No overlaps
482+ let selector = DefaultWorkerSelector :: new ( None ) ;
483+ let block_size = 20 ;
484+
485+ let result = selector
486+ . select_worker ( & workers, & request, block_size)
487+ . expect ( "Should fallback to selecting worker" ) ;
488+
489+ // Worker1 should be selected with 0 overlap
490+ assert_eq ! ( result. worker_id, 1 ) ;
491+ assert_eq ! ( result. overlap_blocks, 0 ) ;
492+ }
493+
494+ #[ test]
495+ fn test_custom_weights ( ) {
496+ // Setup workers
497+ let workers = create_workers ( vec ! [
498+ WorkerInfo {
499+ id: 1 ,
500+ usage: 0.50 ,
501+ waiting: 1 ,
502+ } ,
503+ WorkerInfo {
504+ id: 2 ,
505+ usage: 0.80 ,
506+ waiting: 0 ,
507+ } ,
508+ ] ) ;
509+
510+ // Custom config with high priority on GPU usage
511+ let config = KvRouterConfig {
512+ gpu_cache_usage_weight : 10.0 , // Very high weight
513+ overlap_score_weight : 2.0 , // just current defaults
514+ waiting_requests_weight : 1.0 ,
515+ } ;
516+ let selector = DefaultWorkerSelector :: new ( Some ( config) ) ;
517+ let request = create_request (
518+ vec ! [
519+ WorkerOverlap {
520+ worker_id: 1 ,
521+ overlap_blocks: 3 ,
522+ } ,
523+ WorkerOverlap {
524+ worker_id: 2 ,
525+ overlap_blocks: 4 ,
526+ } ,
527+ ] ,
528+ 100 ,
529+ ) ;
530+ let block_size = 20 ;
531+
532+ let result = selector
533+ . select_worker ( & workers, & request, block_size)
534+ . expect ( "Should select worker" ) ;
535+
536+ assert_eq ! ( result. worker_id, 1 ) ;
537+ }
538+ }
0 commit comments