TestExecWindow
Class Executor - process execution and redirection of output

Overview:

Relevant class attributes

To manage the execution of an external process the following class data are used:

// Control data used for executing an external process
private System.Diagnostics.Process m_process = null;
// Flag indicating that the process has been started
private bool m_processStarted = false;
// Output window pane within Visual Studio to be
// used as stdout for test executable
private EnvDTE.OutputWindowPane m_outputPane = null;
// Flag indicating whether stdout and stderr
// of the started executable shall be redirected and
// a separate execution window shall be suppressed.
bool m_catchStdOutAndStdErr = true;
// Flag indicating whether Executor shall be notified
// when the process terminates
bool m_getNotificationWhenProcessTerminates = true;
// Flag indicating whether after process termination
// the process output is checked for string "Detected memory leaks"
private bool m_checkForMemoryLeaks = false;

Starting a process

public void StartProcess(string exePath, string args, string workDir)
{
try
{
string argsToUse = "";
if (args != null)
{
argsToUse = args;
}
ThreadHelper.ThrowIfNotOnUIThread();
// Ensure executable exists
if (!System.IO.File.Exists(exePath))
{
m_mainEvents.OnTestTerminated(false, exePath, false, new TimeSpan(0));
WriteLine(1, "Executable not found: " + exePath);
//m_eventReceiver.MsgBox("Executable not found: " + exePath);
return;
}
// Ensure output pane exists
if (m_outputPane == null)
{
EnvDTE.OutputWindow ow = MyGlobals.myDTE.ToolWindows.OutputWindow;
m_outputPane = ow.OutputWindowPanes.Add("Run UnitTest");
}
m_outputPane.Activate();
m_outputPane.Clear();
// ----- Prepare process data -----
m_process = new System.Diagnostics.Process();
m_process.StartInfo.FileName = exePath;
m_process.StartInfo.WorkingDirectory = workDir;
m_process.StartInfo.Arguments = argsToUse.Trim();
if (m_getNotificationWhenProcessTerminates)
{
// Remark: Method Executor.Process_Exited will be called
// from some system thread when the process has terminated.
// Synchronization will be needed!
m_process.Exited += new System.EventHandler(Process_Exited);
m_process.EnableRaisingEvents = true;
}
if (m_catchStdOutAndStdErr)
{
m_process.StartInfo.UseShellExecute = false;
m_process.StartInfo.RedirectStandardOutput = true;
m_process.StartInfo.RedirectStandardError = true;
m_process.StartInfo.CreateNoWindow = true;
m_process.OutputDataReceived += new System.Diagnostics.
DataReceivedEventHandler(StandardOutputReceiver);
m_process.ErrorDataReceived += new System.Diagnostics.
DataReceivedEventHandler(StandardErrorReceiver);
}
// ----- Start the new process and start redirection of output -----
m_process.Start();
m_processStarted = true;
if (m_catchStdOutAndStdErr)
{
m_process.BeginOutputReadLine();
m_process.BeginErrorReadLine();
}
WriteLine(2, "Started " + m_process.StartInfo.FileName);
}
catch (Exception e)
{
string info = "EXCEPTION: Could not start executable " + exePath + " " + e.ToString();
WriteLine(1, info);
m_mainEvents.OnTestTerminated(false, exePath, false, new TimeSpan(0));
}
}

Redirection of process output

The methods StandardOutputReceiver and StandardErrorReceiver will receive any output from the started process. On each call they will pass the given data to the output pane of Visual Studio:

private void StandardOutputReceiver(object sendingProcess,
System.Diagnostics.DataReceivedEventArgs outLine)
{
// Receives the child process' standard output within any system thread
// Give time slice to other threads to keep VS GUI responsive in case of output bursts
// (e.g. memory leak messages at termination of program)
if (Services.GetOptions().LimitCpuForStdOut)
{
System.Threading.Thread.Yield();
}
if (!string.IsNullOrEmpty(outLine.Data))
{
// Delegate execution to main (GUI) thread
Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.Run(async delegate
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
if (m_outputPane != null)
m_outputPane.OutputString(outLine.Data + System.Environment.NewLine);
});
}
}

Kill a running process

A running process can be stopped at any time by simply calling:

m_process.Kill();

Even with killing the process the callback function Executor.Process_Exited will be called.