1
1
// Copyright (c) .NET Foundation. All rights reserved.
2
2
// Licensed under the MIT License. See LICENSE in the project root for license information.
3
3
4
- using Azure . Functions . Cli . Interfaces ;
4
+ using System . Runtime . InteropServices ;
5
+ using Azure . Functions . Cli . UnitTests . Helpers ;
5
6
using FluentAssertions ;
6
7
using Xunit ;
7
8
@@ -10,14 +11,15 @@ namespace Azure.Functions.Cli.UnitTests;
10
11
public class CancelKeyHandlerTests
11
12
{
12
13
[ Fact ]
13
- public void FirstCtrlC_ShouldInvokeOnFirstCancel_AndKillChildProcesses ( )
14
+ public void CancelKeyHandler_FirstCancel_ShouldInvokeOnFirstCancel_AndKillChildProcesses ( )
14
15
{
15
16
try
16
17
{
17
18
// Arrange
18
19
bool firstCancelCalled = false ;
19
- var fakeProcessManager = new FakeProcessManager ( ) ;
20
- CancelKeyHandler . Register ( fakeProcessManager , onFirstCancel : ( ) => firstCancelCalled = true ) ;
20
+ var fakeProcessManager = new TestProcessManager ( ) ;
21
+ var stubReader = new TestConsoleReader ( ) ;
22
+ CancelKeyHandler . Register ( fakeProcessManager , stubReader , onFirstCancel : ( ) => firstCancelCalled = true ) ;
21
23
22
24
// Act
23
25
var e = CreateFakeCancelEventArgs ( ) ;
@@ -35,14 +37,15 @@ public void FirstCtrlC_ShouldInvokeOnFirstCancel_AndKillChildProcesses()
35
37
}
36
38
37
39
[ Fact ]
38
- public void SecondCtrlC_ShouldInvokeOnSecondCancel_AndKillMainProcess ( )
40
+ public void CancelKeyHandler_SecondCancel_ShouldInvokeOnSecondCancel_AndKillMainProcess ( )
39
41
{
40
42
try
41
43
{
42
44
// Arrange
43
45
bool secondCancelCalled = false ;
44
- var fakeProcessManager = new FakeProcessManager ( ) ;
45
- CancelKeyHandler . Register ( fakeProcessManager , onSecondCancel : ( ) => secondCancelCalled = true ) ;
46
+ var fakeProcessManager = new TestProcessManager ( ) ;
47
+ var stubReader = new TestConsoleReader ( ) ;
48
+ CancelKeyHandler . Register ( fakeProcessManager , stubReader , onSecondCancel : ( ) => secondCancelCalled = true ) ;
46
49
47
50
// Act
48
51
var e = CreateFakeCancelEventArgs ( ) ;
@@ -60,31 +63,75 @@ public void SecondCtrlC_ShouldInvokeOnSecondCancel_AndKillMainProcess()
60
63
}
61
64
}
62
65
63
- private static ConsoleCancelEventArgs CreateFakeCancelEventArgs ( )
66
+ [ SkippableFact ]
67
+ public void CancelKeyHandler_Windows_FirstCancel_ShouldInvokeFirstOnCancel_AndKillChildProcesses ( )
64
68
{
65
- var constructor = typeof ( ConsoleCancelEventArgs )
66
- . GetConstructors ( System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Instance ) [ 0 ] ;
69
+ if ( ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
70
+ {
71
+ return ; // Skip on non-Windows
72
+ }
67
73
68
- return ( ConsoleCancelEventArgs ) constructor . Invoke ( [ ConsoleSpecialKey . ControlC ] ) ;
74
+ try
75
+ {
76
+ bool firstCancelCalled = false ;
77
+ var fakeProcessManager = new TestProcessManager ( ) ;
78
+ var stubReader = new TestConsoleReader ( ) ;
79
+ CancelKeyHandler . Register ( fakeProcessManager , consoleReader : stubReader , onFirstCancel : ( ) => firstCancelCalled = true ) ;
80
+
81
+ // Act
82
+ var e = CreateFakeCancelEventArgs ( ) ;
83
+ CancelKeyHandler . HandleCancelKeyPress ( null , e ) ; // First Ctrl+C
84
+
85
+ // Assert
86
+ firstCancelCalled . Should ( ) . BeTrue ( ) ;
87
+ fakeProcessManager . KillChildProcessesCalled . Should ( ) . BeTrue ( ) ;
88
+ fakeProcessManager . KillMainProcessCalled . Should ( ) . BeFalse ( ) ;
89
+ }
90
+ finally
91
+ {
92
+ CancelKeyHandler . Dispose ( ) ;
93
+ }
69
94
}
70
- }
71
95
72
- internal class FakeProcessManager : IProcessManager
73
- {
74
- public bool KillChildProcessesCalled { get ; private set ; }
96
+ [ SkippableFact ]
97
+ public async Task CancelKeyHandler_Windows_FirstCancelWithQKey_ShouldInvokeOnSecondCancel_AndKillMainProcess ( )
98
+ {
99
+ if ( ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
100
+ {
101
+ return ; // Skip on non-Windows
102
+ }
75
103
76
- public bool KillMainProcessCalled { get ; private set ; }
104
+ try
105
+ {
106
+ bool secondCancelCalled = false ;
107
+ var fakeProcessManager = new TestProcessManager ( ) ;
108
+ var stubReader = new TestConsoleReader ( ) ;
109
+ CancelKeyHandler . Register ( fakeProcessManager , consoleReader : stubReader , onSecondCancel : ( ) => secondCancelCalled = true ) ;
77
110
78
- public void KillChildProcesses ( ) => KillChildProcessesCalled = true ;
111
+ var e = CreateFakeCancelEventArgs ( ) ;
112
+ CancelKeyHandler . HandleCancelKeyPress ( null , e ) ; // First Ctrl+C
79
113
80
- public void KillMainProcess ( ) => KillMainProcessCalled = true ;
114
+ // Simulate 'q' key press in the background
115
+ stubReader . SimulateKeyPress ( new ConsoleKeyInfo ( 'q' , ConsoleKey . Q , false , false , false ) ) ;
81
116
82
- // Other interface members omitted for this test
83
- public IEnumerable < IProcessInfo > GetProcessesByName ( string processName ) => null ;
117
+ // Briefly wait to allow `q` to be processed
118
+ await Task . Delay ( 50 ) ;
84
119
85
- public IProcessInfo GetCurrentProcess ( ) => null ;
120
+ secondCancelCalled . Should ( ) . BeTrue ( ) ;
121
+ fakeProcessManager . KillChildProcessesCalled . Should ( ) . BeTrue ( ) ;
122
+ fakeProcessManager . KillMainProcessCalled . Should ( ) . BeTrue ( ) ;
123
+ }
124
+ finally
125
+ {
126
+ CancelKeyHandler . Dispose ( ) ;
127
+ }
128
+ }
86
129
87
- public IProcessInfo GetProcessById ( int processId ) => null ;
130
+ private static ConsoleCancelEventArgs CreateFakeCancelEventArgs ( )
131
+ {
132
+ var constructor = typeof ( ConsoleCancelEventArgs )
133
+ . GetConstructors ( System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Instance ) [ 0 ] ;
88
134
89
- public bool RegisterChildProcess ( System . Diagnostics . Process childProcess ) => false ;
135
+ return ( ConsoleCancelEventArgs ) constructor . Invoke ( [ ConsoleSpecialKey . ControlC ] ) ;
136
+ }
90
137
}
0 commit comments