@@ -156,11 +156,12 @@ fn handle_stream_activity(
156156 "Activity detected"
157157 ) ;
158158 let new_deadline = calculate_new_deadline ( timeouts. absolute_deadline , timeouts. activity ) ;
159- if new_deadline != * current_deadline {
159+
160+ if * current_deadline < timeouts. absolute_deadline && new_deadline != * current_deadline {
160161 debug ! ( old = ?* current_deadline, new = ?new_deadline, "Updating deadline" ) ;
161162 * current_deadline = new_deadline;
162163 } else {
163- debug ! ( deadline = ?* current_deadline, "Deadline remains unchanged (likely at absolute limit)" ) ;
164+ debug ! ( deadline = ?* current_deadline, "Deadline remains unchanged (likely at absolute limit or no change )" ) ;
164165 }
165166}
166167
@@ -993,4 +994,135 @@ mod tests {
993994 ) ;
994995 } ) ;
995996 }
997+
998+ // ----- tests for calculate_new_deadline -----
999+ #[ test]
1000+ fn test_calculate_new_deadline_absolute_deadline_passed ( ) {
1001+ let absolute_deadline = Instant :: now ( ) - Duration :: from_secs ( 1 ) ; // Already passed
1002+ let activity_timeout = Duration :: from_secs ( 5 ) ;
1003+
1004+ let new_deadline = calculate_new_deadline ( absolute_deadline, activity_timeout) ;
1005+
1006+ assert_eq ! (
1007+ new_deadline, absolute_deadline,
1008+ "New deadline should be the absolute deadline when it has already passed"
1009+ ) ;
1010+ }
1011+
1012+ #[ test]
1013+ fn test_calculate_new_deadline_activity_timeout_before_absolute_deadline ( ) {
1014+ let absolute_deadline = Instant :: now ( ) + Duration :: from_secs ( 10 ) ;
1015+ let activity_timeout = Duration :: from_secs ( 5 ) ;
1016+
1017+ let new_deadline = calculate_new_deadline ( absolute_deadline, activity_timeout) ;
1018+
1019+ assert ! (
1020+ new_deadline <= absolute_deadline,
1021+ "New deadline should not exceed the absolute deadline"
1022+ ) ;
1023+ assert ! (
1024+ new_deadline > Instant :: now( ) ,
1025+ "New deadline should be in the future"
1026+ ) ;
1027+ }
1028+ // ----- tests for handle_stream_activity -----
1029+ #[ test]
1030+ fn test_handle_stream_activity_updates_deadline ( ) {
1031+ let mut current_deadline = Instant :: now ( ) + Duration :: from_secs ( 5 ) ;
1032+ let timeouts = TimeoutConfig {
1033+ minimum : Duration :: from_secs ( 1 ) ,
1034+ maximum : Duration :: from_secs ( 10 ) ,
1035+ activity : Duration :: from_secs ( 3 ) ,
1036+ start_time : Instant :: now ( ) ,
1037+ absolute_deadline : Instant :: now ( ) + Duration :: from_secs ( 10 ) ,
1038+ } ;
1039+
1040+ handle_stream_activity ( 10 , "stdout" , & mut current_deadline, & timeouts) ;
1041+
1042+ assert ! (
1043+ current_deadline > Instant :: now( ) ,
1044+ "Current deadline should be updated to a future time"
1045+ ) ;
1046+ assert ! (
1047+ current_deadline <= timeouts. absolute_deadline,
1048+ "Current deadline should not exceed the absolute deadline"
1049+ ) ;
1050+ }
1051+
1052+ #[ test]
1053+ fn test_handle_stream_activity_no_update_at_absolute_limit ( ) {
1054+ let absolute_deadline = Instant :: now ( ) + Duration :: from_secs ( 5 ) ;
1055+ let mut current_deadline = absolute_deadline; // Already at the absolute limit
1056+ let timeouts = TimeoutConfig {
1057+ minimum : Duration :: from_secs ( 1 ) ,
1058+ maximum : Duration :: from_secs ( 10 ) ,
1059+ activity : Duration :: from_secs ( 3 ) ,
1060+ start_time : Instant :: now ( ) ,
1061+ absolute_deadline,
1062+ } ;
1063+
1064+ handle_stream_activity ( 10 , "stderr" , & mut current_deadline, & timeouts) ;
1065+
1066+ assert_eq ! (
1067+ current_deadline, absolute_deadline,
1068+ "Current deadline should remain unchanged when at the absolute limit"
1069+ ) ;
1070+ }
1071+
1072+ // ----- tests for run_command_loop -----
1073+ #[ test]
1074+ fn test_run_command_loop_exits_on_process_finish ( ) {
1075+ run_async_test ( || async {
1076+ let mut cmd = StdCommand :: new ( "echo" ) ;
1077+ cmd. arg ( "Test" ) ;
1078+
1079+ let timeouts = TimeoutConfig {
1080+ minimum : Duration :: from_secs ( 1 ) ,
1081+ maximum : Duration :: from_secs ( 5 ) ,
1082+ activity : Duration :: from_secs ( 2 ) ,
1083+ start_time : Instant :: now ( ) ,
1084+ absolute_deadline : Instant :: now ( ) + Duration :: from_secs ( 5 ) ,
1085+ } ;
1086+
1087+ let mut state = spawn_command_and_setup_state ( & mut cmd, timeouts. absolute_deadline )
1088+ . expect ( "Failed to spawn command" ) ;
1089+
1090+ let result = run_command_loop ( & mut state, & timeouts) . await ;
1091+
1092+ assert ! ( result. is_ok( ) , "Command loop should exit without errors" ) ;
1093+ assert ! (
1094+ state. exit_status. is_some( ) ,
1095+ "Exit status should be set when process finishes naturally"
1096+ ) ;
1097+ } ) ;
1098+ }
1099+
1100+ #[ test]
1101+ fn test_run_command_loop_exits_on_timeout ( ) {
1102+ run_async_test ( || async {
1103+ let mut cmd = StdCommand :: new ( "sleep" ) ;
1104+ cmd. arg ( "5" ) ;
1105+
1106+ let timeouts = TimeoutConfig {
1107+ minimum : Duration :: from_secs ( 1 ) ,
1108+ maximum : Duration :: from_secs ( 2 ) , // Short timeout
1109+ activity : Duration :: from_secs ( 10 ) ,
1110+ start_time : Instant :: now ( ) ,
1111+ absolute_deadline : Instant :: now ( ) + Duration :: from_secs ( 2 ) ,
1112+ } ;
1113+
1114+ let mut state = spawn_command_and_setup_state ( & mut cmd, timeouts. absolute_deadline )
1115+ . expect ( "Failed to spawn command" ) ;
1116+
1117+ let result = run_command_loop ( & mut state, & timeouts) . await ;
1118+
1119+ assert ! ( result. is_ok( ) , "Command loop should exit without errors" ) ;
1120+ assert ! (
1121+ state. exit_status. is_none( ) ,
1122+ "Exit status should be None when process is killed due to timeout"
1123+ ) ;
1124+ assert ! ( state. timed_out, "State should indicate that the process timed out" ) ;
1125+ } ) ;
1126+ }
1127+
9961128} // end tests mod
0 commit comments