TestExecWindow
MyToolWindow.cs
//------------------------------------------------------------------------------
// Copyright(C) 2022 Gerald Fahrnholz
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Contact: http://www.gerald-fahrnholz.eu/impressum.php
//------------------------------------------------------------------------------
using Microsoft.VisualStudio.Imaging;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace TestExecWin_VS2022
{
public class MyToolWindow : BaseToolWindow<MyToolWindow>, IEventReceiver, IMainEvents
{
// Helper objects with specific tasks
private VisualStudioConnector m_vsConnector;
private SourceFileParser m_parser;
private int m_outputLevel = 1;
private bool m_writeThreadId = false;
private ProjectInfo m_projectInfo = new ProjectInfo();
private bool m_initialMessageWasDisplayed = false;
//----- Construction and initialization -----
public MyToolWindow()
{
// Remark: this method is called from Toolkit.AsyncPackageExtensions.RegisterToolWindows,
// which is triggered by TestExecWin_VS2022Package.InitializeAsync
MyGlobals.myEventReceiver = this;
MyGlobals.myMainEvents = this;
}
public override string GetTitle(int toolWindowId) => "TestExecWindow";
public override Type PaneType => typeof(Pane);
public override Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken)
{
// Remark: this method is called from ToolkitPackage.InitializeToolWindowAsync
return Task.FromResult<FrameworkElement>(new MyToolWindowControl());
}
//----- Interface IMainEvents -----
public void OnGuiIsStarted()
{
// Create other classes after GUI instantiation because method "WriteLine" is used
// on many code locations and causes output to log pane
m_vsConnector = new VisualStudioConnector(this, this);
m_parser = new SourceFileParser(this);
MyGlobals.myExecutor = new Executor(this, this);
OnRefreshAll();
}
void IMainEvents.OnSettingsHaveChanged()
{
Gui().SomeSettingsExternallyChanged();
WriteOptions();
}
public void OnRefreshAll()
{
// Synchronize via GUI thread
Gui().TriggerDelayedRefresh();
}
void IMainEvents.OnRefreshNow()
{
// If test is still running we do not want to remove current data
if (Gui().TestIsRunning())
{
string info = "WARNING: Ignoring change of startup project while test is running";
WriteLine(1, info);
return;
}
// We assume to be within safe GUI thread
ThreadHelper.ThrowIfNotOnUIThread();
RefreshAll();
}
void IMainEvents.OnSetOutputLevel(int in_level)
{
m_outputLevel = in_level;
m_writeThreadId = m_outputLevel >= 2;
}
void IMainEvents.OnStartBuilding()
{
ThreadHelper.ThrowIfNotOnUIThread();
m_vsConnector.BuildProject();
}
void IMainEvents.OnBuildTerminated(bool success)
{
ThreadHelper.ThrowIfNotOnUIThread();
Gui().BuildTerminated(success);
}
void IMainEvents.OnStartTest(string exePath, string args, string workDir)
{
MyGlobals.myExecutor.StartProcess(exePath, args, workDir); ;
}
/// [test terminated]
public void OnTestTerminated(bool in_success, string in_args, bool in_memLeaksDetected, TimeSpan in_executionTime)
{
// This function is called when the test app terminates.
// The call may arrive on any system thread and needs to be synchronized with the GUI thread.
Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.Run(async delegate
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
Gui().TestTerminatedWithinGuiThread(in_success, "", in_args, in_memLeaksDetected, in_executionTime);
});
}
/// [test terminated]
void IMainEvents.OnStopAll()
{
ThreadHelper.ThrowIfNotOnUIThread();
m_vsConnector.StopBuildingProject();
MyGlobals.myExecutor.KillRunningProcess();
}
void IMainEvents.OnStartDebugging(string in_cmdLineParams)
{
ThreadHelper.ThrowIfNotOnUIThread();
m_vsConnector.StartDebugging(in_cmdLineParams);
}
void IMainEvents.OnOpenProtocolFile()
{
ThreadHelper.ThrowIfNotOnUIThread();
string protocolFilePath = m_projectInfo.GetExePath();
protocolFilePath = protocolFilePath.Replace(".exe", ".out");
if (!System.IO.File.Exists(protocolFilePath))
{
MsgBox("Test protocol not found: " + protocolFilePath);
}
else
{
m_vsConnector.OpenFile(protocolFilePath);
}
}
void IMainEvents.OnOpenSourceFile(string in_fullFileName, int in_lineNum)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (!System.IO.File.Exists(in_fullFileName))
{
MsgBox("Sourcefile not found: " + in_fullFileName);
}
else
{
m_vsConnector.OpenFile(in_fullFileName, in_lineNum);
}
}
string IMainEvents.OnGetExecutablesFromCurrentSolution()
{
ThreadHelper.ThrowIfNotOnUIThread();
return m_vsConnector.GetExecutablesFromCurrentSolution();
}
//----- Interface IEventReceiver -----
public void WriteLine(int in_outputLevel, String in_info)
{
if (in_outputLevel <= m_outputLevel)
{
if (m_writeThreadId)
{
Gui().AddInfoToEventList(Services.GetThreadIdPrompt() + in_info);
}
else
{
Gui().AddInfoToEventList(in_info);
}
}
}
public void MsgBox(string in_info)
{
System.Windows.MessageBox.Show(in_info, "Info");
}
//----- Helper methods -----
private void RefreshAll()
{
WriteLine(3, "RefreshAll-Begin");
try
{
ThreadHelper.ThrowIfNotOnUIThread();
if(!m_initialMessageWasDisplayed)
{
m_initialMessageWasDisplayed = true;
var options = Services.GetOptions();
WriteLine(1, "Options: Build before test=" + options.BuildSolutionBeforeStartingTest.ToString()
+ ", Check for memory leaks = " + options.CheckForMemoryLeaks.ToString());
}
// Reset data
m_projectInfo = new ProjectInfo();
if (!m_vsConnector.ReadSettingsOfStartupProject(m_projectInfo))
{
// MsgBox("There is no startup project defined or the project settings are not accessible!");
WriteLine(2, "WARNING: There is no startup project defined or the project settings are not accessible!");
return;
}
m_parser.ScanProjectDir(m_projectInfo);
Gui().SetTestInfo(m_projectInfo);
WriteLine(1, "Detected project " + m_projectInfo.project + " (" + m_projectInfo.config + ")"
+ ", type is " + m_projectInfo.appType
+ ", " + m_projectInfo.testGroups.Count + " test groups, "
+ m_projectInfo.testFuncs.Count + " test funcs");
}
catch (Exception e)
{
string info = "EXCEPTION: " + e.ToString();
WriteLine(1, info);
}
WriteLine(3, "RefreshAll-End");
}
private void WriteOptions()
{
var options = Services.GetOptions();
WriteLine(2, "Options: Build before test=" + options.BuildSolutionBeforeStartingTest.ToString()
+ ", Check for memory leaks = " + options.CheckForMemoryLeaks.ToString());
WriteLine(2, "Options: Limit CPU for std out =" + options.LimitCpuForStdOut.ToString());
WriteLine(2, "Options: Run until failure =" + options.RunUntilFailure.ToString());
}
MyToolWindowControl Gui()
{
return MyGlobals.myToolWindowControl;
}
[Guid("8b4ef7af-4568-4c07-ba13-89dd69794c1f")]
internal class Pane : ToolWindowPane
{
public Pane()
{
// Remark: code is not used?
// Icon (in color green) is now controlled by old fashioned bitmap
// within VSCommandTable.vsct
BitmapImageMoniker = KnownMonikers.ConsoleTest;
}
}
}
}