locked
Unit Test coverage for Console.CancelKeyPress in VS 2012 .NET 4.5 RRS feed

  • Question

  • I am trying to hit the delegate method I assign to Console.CancelKeyPress in Visual Studio 2012 for a console application using .NET 4.5. I am using Visual Studio 2012's 'Analyze Code Coverage' for select test to validate what code is actually executed.

    The program I am exercising is fairly simple:

     

        /// <summary>
        /// The main execution program.
        /// </summary>
        public sealed class Program
        {
            /// <summary>
            /// The shutdown request event.
            /// </summary>
            private static readonly AutoResetEvent ShutdownRequest = new AutoResetEvent(false);

            /// <summary>
            /// The main routine.
            /// </summary>
            /// <param name="args">
            /// The program start args.
            /// </param>
            /// <returns>
            /// The <see cref="int"/>.
            /// </returns>
            public static int Main(string[] args)
            {
                // Listen to shutdown requests
                Console.CancelKeyPress += () => { ShutdownRequest.Set(); };
                var autoResetEvent = new AutoResetEvent(false);
                autoResetEvent.WaitOne(5000);
                return 0;
            }
        }

    I have verified that hitting Ctrl + C when the console application window shows up triggers the delegate.

    I attempted a couple of ways using win32 APIs native methods:

        using System.Runtime.InteropServices;

        /// <summary>
        /// Native methods.
        /// </summary>
        internal static class NativeMethods
        {
            /// <summary>
            /// The console ctrl event.
            /// </summary>
            public enum ConsoleCtrlEvent
            {
                /// <summary>
                /// The ctrl c event.
                /// </summary>
                CtrlC = 0,

                /// <summary>
                /// The ctrl break event.
                /// </summary>
                CtrlBreak = 1,

                /// <summary>
                /// The ctrl close event.
                /// </summary>
                CtrlClose = 2,

                /// <summary>
                /// The ctrl logoff event.
                /// </summary>
                CtrlLogoff = 5,

                /// <summary>
                /// The ctrl shutdown event.
                /// </summary>
                CtrlShutdown = 6
            }

            /// <summary>
            /// The unmanaged keyboard event method.
            /// </summary>
            /// <param name="virtualKeyCode">
            /// The virtual key code.
            /// </param>
            /// <param name="hardwareScanCode">
            /// The hardware scan code.
            /// </param>
            /// <param name="customFunctionParameter">
            /// The custom function parameter.
            /// </param>
            /// <param name="extraInfo">
            /// The extra info.
            /// </param>
            [DllImport("User32.dll", EntryPoint = "keybd_event", SetLastError = true)]
            public static extern void KeyboardEvent(byte virtualKeyCode, byte hardwareScanCode, int customFunctionParameter, int extraInfo);

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GenerateConsoleCtrlEvent(ConsoleCtrlEvent sigevent, int dwProcessGroupId);
        }

            /// <summary>
            /// Test the cancellation within the current process.
            /// </summary>
            [TestMethod]
            public void CancelFromWithin()
            {
                var mainThread = new Thread(() => Program.Main(new[] { string.Empty }));
                mainThread.Start();

                // This disabled code does not work because you can't redirect standard output for 'outside' once a process is running.
                ////Process.GetCurrentProcess().StartInfo.RedirectStandardInput = true;
                ////Process.GetCurrentProcess().StandardInput.WriteLine("\x3");

                NativeMethods.KeyboardEvent(0x11, 0, 0, 0);
                NativeMethods.KeyboardEvent(0x43, 0, 0, 0);
                NativeMethods.KeyboardEvent(0x11, 0, 2, 0);
                NativeMethods.KeyboardEvent(0x43, 0, 2, 0);

                // Another method. Possibly not leading to the expected results because vstest.console swallows the input?
                NativeMethods.GenerateConsoleCtrlEvent(NativeMethods.ConsoleCtrlEvent.CtrlC, Process.GetCurrentProcess().SessionId);
                using (var memoryStream = new MemoryStream(Encoding.Default.GetBytes("\x3")))
                {
                    var textReader = new StreamReader(memoryStream);
                    Console.SetIn(textReader);
                    mainThread.Join();
                }
            }

    I found also a more elegant way to try to do it within managed code using input redirection and a separate process:

            /// <summary>
            /// Test the cancellation by creating a distinct process.
            /// </summary>
            [TestMethod]
            public void Cancel()
            {
                using (var process = new Process())
                {
                    {
                        process.StartInfo = new ProcessStartInfo
                        {
                            FileName = "test.exe",
                            UseShellExecute = false,
                            RedirectStandardInput = true,
                            RedirectStandardOutput = true,
                            RedirectStandardError = true
                        };
                        process.Start();
                        Console.WriteLine("{0} is active: {1}", process.Id, !process.HasExited);
                        process.StandardInput.AutoFlush = true;
                        process.StandardInput.Write("^(C)");
                        process.StandardInput.WriteLine("^(C)");
                        process.StandardInput.Write("\x3");
                        process.StandardInput.WriteLine("\x3");
                        NativeMethods.GenerateConsoleCtrlEvent(NativeMethods.ConsoleCtrlEvent.CtrlC, process.SessionId);
                        NativeMethods.GenerateConsoleCtrlEvent(NativeMethods.ConsoleCtrlEvent.CtrlBreak, process.SessionId);
                        process.StandardInput.Close();
                        Console.WriteLine("{0} is active: {1}", process.Id, !process.HasExited);
                        process.StandardOutput.ReadToEnd();
                        process.WaitForExit();
                    }
                }
            }

    Yes, I overloaded the cancellation attempts to 6 variations - throwing things and seeing what sticks.

    But none of that hit the cancellation delegate, so I modified my code to make the delegate public such that it can be called directly from the unit test:

            /// <summary>
            /// The cancel key press delegate.
            /// </summary>
            /// <param name="sender">
            /// The sender.
            /// </param>
            /// <param name="consoleCancelEventArgs">
            /// The console cancel event args.
            /// </param>
            public static void CancelKeyPressDelegate(object sender, ConsoleCancelEventArgs consoleCancelEventArgs)
            {
                ShutdownRequest.Set();
            }

    Then in main:

                Console.CancelKeyPress += Program.CancelKeyPressDelegate;

    And the matching unit test:

            /// <summary>
            /// Test the cancellation within the current process.
            /// </summary>
            [TestMethod]
            public void CancelWithTestabilityDelegate()
            {
                var mainThread = new Thread(() => Program.Main(new[] { string.Empty }));
                mainThread.Start();
                Program.CancelKeyPressDelegate(null, null);
                mainThread.Join();
            }

    While I did get the code coverage I was looking for, I would feel more comfortable in my testing if I could reach the code through emulating a Ctrl + C or Ctrl + Break input. Others seem to have success using the different variations I attempted. What do I not see the delegate code been covered by my unit tests?


    Senior Soft. Dev. Eng. | Microsoft IT | Microsoft Corporation


    • Edited by David Burg Tuesday, January 15, 2013 5:31 PM Removing some internal comment
    Tuesday, January 15, 2013 5:29 PM

Answers

  • Hello David,

    I would like to know if you want to write code to emulate Ctrl+C for shutting down the console window in unit test which is accomplished using a ShutDownRequest.If yes, I suggest that you can consult this issue on development forum such as Visual C# General forum to check if there is a way to support emulating Ctrl+C input programtically in the console application itself. I am afraid that it is out of support of VS testing. If there is a way to do that, then you can try to accomplish it with unit test based on the way.

    Best regards,


    Amanda Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.



    • Edited by Amanda Zhu Tuesday, January 22, 2013 10:40 AM
    • Marked as answer by Amanda Zhu Friday, February 1, 2013 9:47 AM
    Tuesday, January 22, 2013 3:04 AM

All replies

  • Hello David,

    Thank you for your post.

    I would like to know if the unit test result (true or false) is expected. If yes, and you can get the code coverage result after running test without errors, I am afraid that the issue is not related to your unit test.

    Generally we analyze the code coverage to determine what proportion of your project code is actually being tested in unit test.

    If the code is not covered, it means that the code is not tested during unit test. I suggest that you should check the logic of the project code and make sure unit test will execute through the code.

    If the code is covered, it means that the code is tested during unit test. And the code is needed during unit test execution. You wll see it in code coverage result. If you don't see the code coverage of this code, I think that you need to modiby your project code and make sure the unit test will not executed through the code.

    Also you can custom your own code coverage collection. You can exclued some methods of a dll if you don't want to collect code coverage for them.

    For more information, pleasee:Customizing Code Coverage Analysis

    Using Code Coverage to Determine How Much Code is being Tested

    Best regards,


    Amanda Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.




    • Edited by Amanda Zhu Wednesday, January 16, 2013 7:37 AM
    Wednesday, January 16, 2013 5:34 AM
  • Thank you for your answer Amanda. However I am struggling to make sense of it.

    I do know that the logic of the project code is correct because I can manually test typing Ctrl + C or Ctrl + Break with focus to the console application and validate then through code coverage that the code is reached.

    However, I am not able to programmatically reproduce this input (Ctrl + C) from the unit test to the tested console application. By that I mean that using the various described methods above, and using code coverage to validate what got executed, I see that the code is not reached.

    Yes, the unit test execution status in Visual Studio is passed. (I don't have output validation in place, so the success status only means that the code didn't crash or hang.)


    Senior Soft. Dev. Eng. | Microsoft IT | Microsoft Corporation

    Thursday, January 17, 2013 7:01 PM
  • Hello David,

    Glad to receive your reply.

    I would like to know if you have done unit test for CancelKeyPressDelegate() method to check the Ctrl+C /Break input logic. If yes, it seems that there is anything wrong with your unit test.

    You can refer to this article about creating and running unit test for Managed Code:

    Walkthrough: Creating and Running Unit Tests for Managed Code

    And note that you need to pass the valid parameters into the CancelKeyPressDelegate() method in your test method rather than null.

    If still no help, you can provide us a screen shot about the Code Coverage result so that we can know that which code lines are not reached during test execution.

    Best regards,


    Amanda Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, January 18, 2013 2:21 AM
  • Hello David,

    What about your issue now? Can you tell us the result of the suggestion?

    Best regards,


    Amanda Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Sunday, January 20, 2013 5:14 AM
  • Hi Amanda,

    I do not understand what you mean by "[do] unit test for CancelKeyPressDelegate() method to check the Ctrl+C /Break input logic."

    I can and did write an unit test calling CancelKeyPressDelegate directly, which passes with success and results in code coverage for the said method. Because I am not using the input parameters for that method, I am able to pass null.

    This is a screenshot of code coverage when attempting the input redirection method (not when running a unit test calling CancelKeyPressDelegate directly). As you will see the delegate code is not reached. Maybe the confusion comes from the issue possibly not being with the unit test framework of visual studio, but simply on how to properly simulate Ctrl+C input in C# for a console application.


    Senior Soft. Dev. Eng. | Microsoft IT | Microsoft Corporation

    Monday, January 21, 2013 6:13 PM
  • Hello David,

    I would like to know if you want to write code to emulate Ctrl+C for shutting down the console window in unit test which is accomplished using a ShutDownRequest.If yes, I suggest that you can consult this issue on development forum such as Visual C# General forum to check if there is a way to support emulating Ctrl+C input programtically in the console application itself. I am afraid that it is out of support of VS testing. If there is a way to do that, then you can try to accomplish it with unit test based on the way.

    Best regards,


    Amanda Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.



    • Edited by Amanda Zhu Tuesday, January 22, 2013 10:40 AM
    • Marked as answer by Amanda Zhu Friday, February 1, 2013 9:47 AM
    Tuesday, January 22, 2013 3:04 AM
  • Hi Amanda,

    Are you able to move this thread to Visual C# General forum to follow-up on confirming there if the emulation of Ctrl + C is proper or not?

    With regards,

    David.


    Senior Soft. Dev. Eng. | Microsoft IT | Microsoft Corporation

    Monday, January 28, 2013 6:59 PM
  • Hello David,

    I think that it would be better if you open up a new thread for the new question in the appropriate forum. In this way, it is more convenient and efficient for those experts of another forum to help you resolve your issue. This also will make answer searching in forums easier and be beneficial to other community members as well.

    Thank you for your understanding.

    Best regards,


    Amanda Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, January 29, 2013 1:35 AM