C# - 向子进程发送输入 - 控制台应用程序

Posted

技术标签:

【中文标题】C# - 向子进程发送输入 - 控制台应用程序【英文标题】:C# - Send input to child process - console app 【发布时间】:2020-08-28 22:33:33 【问题描述】:

背景资料

我正在编写一个生成子进程(c# 控制台应用程序)的集成测试。测试在进程启动和进程关闭后计算数据库中的一些行。该进程通过 process.Kill() 关闭

当进程以这种方式被杀死时,它不会在进程中点击 Stop 方法。我需要调用此停止方法来停止线程并从数据库中删除条目以使测试通过。

原始码

我在测试中生成的控制台应用程序进程:
static void Main(string[] args)
    
        TaskManager tm = new TaskManagerProcess();

        if (Environment.UserInteractive ||
            (args.EmptyForNull().Any(a => a.Equals("-RunInteractive", StringComparison.OrdinalIgnoreCase) || a.Equals("/RunInteractive"))))
        
            tm.ConsoleStart(args);
            Console.WriteLine("Press [Enter] to shut down, any other key to mark");
            while (true)
            
                ConsoleKeyInfo key = Console.ReadKey(true);
                if (key.Key == ConsoleKey.Enter)
                    break;
                Console.WriteLine("========================================================");
                Console.Out.Flush();
            
            Console.WriteLine("Shutting down...");
            tm.ConsoleStop();
        
        else
        
            ServiceBase.Run(tm);
        
    

测试代码:

        //count before starting child proc
        int preCount;
        //count after process is spun up
        int runningsCount;
        //count after stopped
        int postCount;

        //Get an initial count of the logged in modules before svc host is started
        user = ApiMethod.GetLoggedInUsers().Where(x => x.RecId == userRecID).FirstOrDefault();
        preCount = user.LoggedInModules.Count;

        Process proc = Helper.StartProcess(ConnectionBundle);

        //Give process time to spin up leaders and workers
        await Task.Delay(TimeSpan.FromSeconds(30));

        //Get a count of modules after process is spun up
        user = ApiMethod.GetLoggedInUsers().Where(x => x.RecId == userRecID).FirstOrDefault();
        runningCount = user.LoggedInModules.Count;

        //Write a line terminator to the child svc host process -
        //this allows it to shutdown normally
        Helper.ProcessInput.WriteLine();
        Helper.ProcessInput.Close();

        Helper.KillProcess(proc);

        await Task.Delay(TimeSpan.FromSeconds(5));

        //Get count of logged in modules after process is closed
        user = ApiMethod.GetLoggedInUsers().Where(x => x.RecId == userRecID).FirstOrDefault();
        postCount = user.LoggedInModules.Count;

Helper 是一个静态类,用于设置进程启动信息(包括 args)并启动进程。在助手中,我重定向了 StandardInput 并添加了一个属性 ProcessInput,该属性设置为已创建进程的 StandardInput。

我的目标是将“Enter”的输入从测试发送到生成的进程,以便它从循环中中断并调用 tm.ConsoleStop()

TaskManagerProcess 是控制流程的私有自定义类。它不继承自 System.Diagnostics.Process。作为一种替代方法,我的测试可以直接与 TaskManagerProcess 交互。但是,我不能公开 TaskManagerProcess,我需要在它自己的 AppDomain 中运行 TaskManagerProcess,因为调用 ConsoleStop 会在 API 中处理我需要完成测试的对象。

我尝试过的事情

    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(CloseProcDelgate handler, bool add);

我尝试添加对 Kernel32.SetConsoleCtrlHandler (和必要的委托)的调用以在进程退出时调用 ConsoleStop。当通过 process.Kill() 杀死进程时,这似乎不起作用

使用原始流程代码,我在写到 StandardInput 时注意到了一个异常。异常消息告诉我使用 Console.Read 而不是 Console.ReadKey()。这实际上是间歇性的!我有时可以在 int cKey = Console.Read() 上获得一个断点(调试器附加到子进程),但其他时候它没有达到断点。

while (true)
            
                //Changing this to Console.Read instead of Console.ReadKey
                //Allows us to send redirected input to process?
                int cKey = Console.Read();
                if ((ConsoleKey)cKey == ConsoleKey.Enter)
                    break;

                Console.WriteLine("========================================================");
                Console.Out.Flush();
            

最后,我尝试直接与 TaskManagerProcess 交互。我将私有类设为内部,并将内部标记为对我的测试程序集可见。我不能公开课程。

当我走这条路时,调用 tm.ConsoleStop() 会吹走我的 API 中的一些对象,因此在调用此方法后我无法检查计数。出于这个原因,我想我会创建一个新的 AppDomain 并在 TaskManagerProcess 类上调用 AppDomain.CreateInstanceAndUnwrap()。但是,我在这里遇到了一个例外,我相信这是因为该类是内部的。

我真的被困在这一点上!感谢您提供任何帮助,并感谢您花时间阅读本文!

编辑

我创建了一个演示项目here 这显示了我正在尝试做的事情,并且在测试方法中有两种方法。

最初我以为我不能调用 AppDomain.CreateInstanceAndUnwrap() 因为 TaskManagerProcess 类是内部的。但是,在玩了我的演示项目之后,我想我就是无法加载程序集。

【问题讨论】:

您是否编写了这个其他应用程序?如果是,只需设置 IPC...无论如何我的感觉是您不应该为另一个应用程序编写测试,您应该测试其中的方法 嗨,Michael,我的公司编写了另一个应用程序。修复错误后,我正在为应用程序编写测试。我会研究IPC,谢谢 您应该为应用程序中的方法编写测试,而不是为控制台应用程序编写测试 Some example 【参考方案1】:

我在这里猜测,但我相信您的 TaskManagerProcess 是一个服务应用程序。如果不是,请忽略这个。如果是,请注意在您的问题中包含此类详细信息。调试服务应用程序可能很复杂,相信我,我曾经去过那里。但在继续之前,请提供更多建议。

测试模块中的方法,而不是整个运行程序,正如 Michael Randall 刚才所说的那样。 除非绝对必要,否则不要对数据库进行测试。模拟测试代码所需的任何内容。

您应该回到与 TaskManagerProcess 直接交互的替代方法。从控制台应用程序的代码中,我看到调用的唯一工作方法是 tm.ConsoleStart(args),循环内的其余部分是控制台写入和读取。所以你不能改变那个班级的访问级别,再说一次,我去过那里。我过去为克服这个问题所做的是使用条件编译在我的私有或内部模块中创建一种公共外观。

假设你有:

internal class TaskManagerContainer


    private class TaskManagerProcess
    
        internal void Start()
        
            // stuff
        

        private void DoSomething(int arg)
        
            // more stuff
        
    

改成这样:

#define TEST

// Symbol TEST can also be defined using the GUI of your IDE or compiler /define option

internal class TaskManagerContainer


//
#if TEST
    public class TaskManagerProcess
#else
    private class TaskManagerProcess
#endif
    
        internal void Start()
        
            // stuff
        

        private void DoSomething(int arg)
        
            // more stuff
        

        #region Methods Facade for Testing

#if TEST
        public void Start_Test()
        
            Start();
        

        private void DoSomething_Test(int arg)
        
            DoSomething(arg);
        
#endif
        #endregion
    

我真的希望它可以帮助您使测试程序集的方法可见,并且不会破坏您 API 中的对象。

【讨论】:

您好,感谢您的回复。我可以通过将类标记为内部并在 assemblyInfo.cs 中将内部设置对我的测试项目可见来绕过使类可见。但是,我相信我需要在自己的 AppDomain 中运行 TaskManagerProcess。如果我只是在我的测试类中创建一个新实例并调用 ConsoleStop,它会在静态 API 中处理一些东西。请原谅我的无知,但我不确定上述示例如何阻止我的 API 对象被释放? 对于数据库,我们有一个设置,可以在测试开始时生成一个测试数据库。测试完成后,测试数据库将被删除。我们几乎所有的集成测试都产生了这个测试数据库。我们甚至有一些在子进程中创建控制台应用程序的现有测试,但是这些测试并不关心应用程序是否正确关闭(tm.ConsoleStop())。如果你觉得无聊,我用这两种方法创建了一个演示解决方案,展示了我在这里尝试做的事情:github.com/jmaness52/Process_Communication 谢谢你的帮助! 对不起,我想我没抓住重点。代码用于更改类的访问级别。万岁玛丽:您是否尝试过在控制台应用程序中设置延迟,以便它自行退出循环,并避免发送击键的需要?【参考方案2】:

我想我是通过蛮力方法得到的。

 while (!testProcess.HasExited)
 
     testProcess.StandardInput.WriteLine();
 

感谢大家的意见!

【讨论】:

以上是关于C# - 向子进程发送输入 - 控制台应用程序的主要内容,如果未能解决你的问题,请参考以下文章

C# 是不是可以从后台进程的控制台应用程序发送 Toast / BalloonTip?

shell向子进程发送信号

C# 子线程 传值

当所有进程都在 c# 的单独线程上运行时,如何保持控制台应用程序打开?

向子组件发送一次事件

如何使用标准输入和标准输出在 C# 和 ruby​​ 进程之间进行通信