@@ -228,6 +228,7 @@ private static async Task RunCommandTests()
228228 await RunTest ( "Streaming command exit code" , TestStreamingCommandExitCode ) ;
229229 await RunTest ( "Streaming async command" , TestStreamingCommandAsync ) ;
230230 await RunTest ( "Streaming incremental output" , TestStreamingIncrementalOutput ) ;
231+ await RunTest ( "Streaming long output (2 min wait)" , TestStreamingLongOutputWithTimeout ) ;
231232 }
232233
233234 private static Task < bool > TestSimpleCommand ( )
@@ -500,6 +501,77 @@ private static async Task<bool> TestStreamingIncrementalOutput()
500501 return result . ExitCode == 0 ;
501502 }
502503
504+ private static async Task < bool > TestStreamingLongOutputWithTimeout ( )
505+ {
506+ // This is an optional long-running test, skip unless explicitly enabled
507+ if ( Environment . GetEnvironmentVariable ( "LONG_RUNNING_STREAM_TEST" ) == null )
508+ {
509+ AnsiConsole . MarkupLine ( "[yellow] Skipped (set LONG_RUNNING_STREAM_TEST=1 to enable)[/]" ) ;
510+ skippedTests ++ ;
511+ return true ;
512+ }
513+
514+ AnsiConsole . MarkupLine ( "[dim] Starting long output streaming test (2 min timeout)...[/]" ) ;
515+
516+ using var session = TestHelper . CreateConnectAndAuthenticate ( ) ;
517+ AnsiConsole . MarkupLine ( "[dim] Session authenticated, executing streaming command...[/]" ) ;
518+
519+ // Execute a command that produces output over 2 minutes (24 lines, 5 seconds apart)
520+ using var stream = await session . ExecuteCommandStreamingAsync (
521+ "for i in $(seq 1 24); do echo \" Line $i at $(date +%H:%M:%S)\" ; sleep 5; done" ) ;
522+ AnsiConsole . MarkupLine ( "[dim] Command started, reading stdout with StreamReader (~2 min output, 3 min timeout)...[/]" ) ;
523+
524+ var lines = new List < string > ( ) ;
525+ var stopwatch = Stopwatch . StartNew ( ) ;
526+
527+ using var reader = new StreamReader ( stream . Stdout ) ;
528+ var lineNum = 0 ;
529+
530+ while ( await reader . ReadLineAsync ( ) is { } line )
531+ {
532+ lineNum ++ ;
533+ lines . Add ( line ) ;
534+ var escapedLine = Markup . Escape ( line ) ;
535+ if ( escapedLine . Length > 60 ) escapedLine = escapedLine [ ..57 ] + "..." ;
536+ AnsiConsole . MarkupLine ( $ "[dim] Line #{ lineNum } : \" { escapedLine } \" at { stopwatch . ElapsedMilliseconds } ms[/]") ;
537+ }
538+
539+ stopwatch . Stop ( ) ;
540+ AnsiConsole . MarkupLine ( $ "[dim] Stream reading complete. Total lines: { lines . Count } , Total time: { stopwatch . ElapsedMilliseconds } ms[/]") ;
541+
542+ var result = stream . WaitForExit ( ) ;
543+ AnsiConsole . MarkupLine ( $ "[dim] Command exited with code: { result . ExitCode } [/]") ;
544+
545+ // Verify we received all 24 lines
546+ if ( lines . Count != 24 )
547+ {
548+ AnsiConsole . MarkupLine ( $ "[red] FAILED: Expected 24 lines, got { lines . Count } [/]") ;
549+ return false ;
550+ }
551+
552+ // Verify the content contains expected line markers
553+ for ( int i = 1 ; i <= 24 ; i ++ )
554+ {
555+ if ( ! lines . Any ( l => l . Contains ( $ "Line { i } at") ) )
556+ {
557+ AnsiConsole . MarkupLine ( $ "[red] FAILED: Missing 'Line { i } ' in output[/]") ;
558+ return false ;
559+ }
560+ }
561+
562+ // Verify timing: total time should be at least 115 seconds (24 lines with 5 second delay each = ~120s)
563+ if ( stopwatch . Elapsed < TimeSpan . FromSeconds ( 115 ) )
564+ {
565+ AnsiConsole . MarkupLine ( $ "[red] FAILED: Total time ({ stopwatch . ElapsedMilliseconds } ms) too short, expected at least 115000ms[/]") ;
566+ return false ;
567+ }
568+
569+ AnsiConsole . MarkupLine ( $ "[green] -> [/] Received { lines . Count } lines in { stopwatch . Elapsed . TotalSeconds : F1} s (~2 min)") ;
570+ AnsiConsole . MarkupLine ( $ "[green] -> [/] All output received successfully") ;
571+
572+ return result . ExitCode == 0 ;
573+ }
574+
503575 #endregion
504576
505577 #region File Transfer Tests
@@ -756,6 +828,7 @@ private static async Task RunEdgeCaseTests()
756828 await RunTest ( "Timeout test" , TimeoutTest ) ;
757829 await RunTest ( "Won't connect with deprecated methods" , WontConnectWithDeprecatedMethods ) ;
758830 await RunTest ( "Parallel sessions test" , ParallelSessionsTest ) ;
831+ await RunTest ( "Trace handler test" , TestTraceHandler ) ;
759832 }
760833
761834 private static Task < bool > ParallelSessionsTest ( )
@@ -879,6 +952,62 @@ private static Task<bool> TestMultipleOperations()
879952 return Task . FromResult ( true ) ;
880953 }
881954
955+ private static Task < bool > TestTraceHandler ( )
956+ {
957+ var traceMessages = new List < string > ( ) ;
958+
959+ using var session = TestHelper . CreateAndConnect ( ) ;
960+
961+ // Enable tracing with our handler
962+ session . SetTrace (
963+ SshTraceLevel . Authentication | SshTraceLevel . Error | SshTraceLevel . KeyExchange ,
964+ message => traceMessages . Add ( message ) ) ;
965+
966+ // Authenticate - this should generate trace messages
967+ var credential = SshCredential . FromPassword ( TestConfig . Username , TestConfig . Password ) ;
968+ var authResult = session . Authenticate ( credential ) ;
969+
970+ if ( ! authResult )
971+ {
972+ AnsiConsole . MarkupLine ( "[red] Authentication failed[/]" ) ;
973+ return Task . FromResult ( false ) ;
974+ }
975+
976+ // Run a simple command
977+ var result = session . ExecuteCommand ( "echo 'trace test'" ) ;
978+
979+ if ( ! result . Successful )
980+ {
981+ AnsiConsole . MarkupLine ( "[red] Command execution failed[/]" ) ;
982+ return Task . FromResult ( false ) ;
983+ }
984+
985+ // Disable tracing
986+ session . SetTrace ( SshTraceLevel . None , null ) ;
987+
988+ AnsiConsole . MarkupLine ( $ "[green] -> [/] Captured { traceMessages . Count } trace messages") ;
989+
990+ // We should have received some trace messages during auth
991+ if ( traceMessages . Count == 0 )
992+ {
993+ AnsiConsole . MarkupLine ( "[red] No trace messages received[/]" ) ;
994+ return Task . FromResult ( false ) ;
995+ }
996+
997+ // Print first few trace messages for debugging
998+ foreach ( var msg in traceMessages . Take ( 5 ) )
999+ {
1000+ var escaped = Markup . Escape ( msg . Trim ( ) ) ;
1001+ if ( escaped . Length > 70 ) escaped = escaped [ ..67 ] + "..." ;
1002+ AnsiConsole . MarkupLine ( $ "[dim] { escaped } [/]") ;
1003+ }
1004+
1005+ if ( traceMessages . Count > 5 )
1006+ AnsiConsole . MarkupLine ( $ "[dim] ... and { traceMessages . Count - 5 } more[/]") ;
1007+
1008+ return Task . FromResult ( true ) ;
1009+ }
1010+
8821011 #endregion
8831012
8841013 #region Test Infrastructure
0 commit comments