Click here to Skip to main content
15,878,871 members
Articles / Web Development / HTML

NTime - Performance unit testing tool

Rate me:
Please Sign up or sign in to vote.
4.73/5 (27 votes)
30 Mar 20066 min read 431.3K   8.2K   163  
An article on a performance testing tool to test an application against its performance
#region Copyright (c) 2004, Adam Slosarski
/************************************************************************************
'
' Copyright  2004 Adam Slosarski
'
' This software is provided 'as-is', without any express or implied warranty. In no 
' event will the authors be held liable for any damages arising from the use of this 
' software.
' 
' Permission is granted to anyone to use this software for any purpose, including 
' commercial applications, and to alter it and redistribute it freely, subject to the 
' following restrictions:
'
' 1. The origin of this software must not be misrepresented; you must not claim that 
' you wrote the original software. If you use this software in a product, an 
' acknowledgment (see the following) in the product documentation is required.
'
' Portions Copyright  2004 Adam Slosarski
'
' 2. Altered source versions must be plainly marked as such, and must not be 
' misrepresented as being the original software.
'
' 3. This notice may not be removed or altered from any source distribution.
'
'***********************************************************************************/
#endregion
using System;
using System.Windows.Forms;
using System.Reflection;
using System.Collections;
using NTime.GUI;
using NTime.Framework;

namespace NTime
{
	/// <summary>
	/// This class performs common UI tasks invoked by user interaction.
	/// </summary>
	public class NTimeMain
	{
		/// <summary>
		/// Status info used in status bar.
		/// </summary>
		private string statusInfo;

		/// <summary>
		/// Total tests used in status bar.
		/// </summary>
		public int totalTests;

		/// <summary>
		/// Number of accepted tests used in status bar.
		/// </summary>
		public int acceptedTests;

		/// <summary>
		/// Number of rejected tests used in status bar.
		/// </summary>
		public int rejectedTests;

		/// <summary>
		/// Total test time used in status bar.
		/// </summary>
		public TimeSpan totalTestsTime;

		/// <summary>
		/// Proxy class to access application domain assemblies.
		/// </summary>
		public RemoteLoader rl;

		/// <summary>
		/// Application domain class to manage assemblies in isolated application domain.
		/// </summary>
		private AssemblyDomain Domain;

		/// <summary>
		/// Assembly filenames loaded into project. This list is used by <see cref="Reload"/> method to refresh assemblies with updated test classes and methods.
		/// </summary>
		public ArrayList AssemblyNames = new ArrayList();

		/// <summary>
		/// Main NTime form object to reference UI objects.
		/// </summary>
		public GUI.NTimeForm Form;

		/// <summary>
		/// Indicates whether project has changed.
		/// </summary>
		private bool dirtyProject = false;
		
		/// <summary>
		/// Indicates whether project name is default and need to be saved with different filename.
		/// </summary>
		private bool firstUse = true;
		
		/// <summary>
		/// Project filename.
		/// </summary>
		public string ProjectName;
		
		/// <summary>
		/// Watches changes in assemblies then refreshes them in project.
		/// </summary>
		private ArrayList directoryWatcher = new ArrayList();
		
		/// <summary>
		/// Indicating whether tests are running now.
		/// </summary>
		public bool Running;

		/// <summary>
		/// Configuration filename for application domain of loaded tests.
		/// </summary>
		public string ConfigurationFilename;

		/// <summary>
		/// Initializes a new instance of main underlayed class to perform common tasks.
		/// </summary>
		public NTimeMain()
		{
			Domain = new AssemblyDomain();
		}

		/// <summary>
		/// Refreshes caption form to show project name (project filename).
		/// </summary>
		private void UpdateCaption()
		{
			if(dirtyProject)
				Form.Text = System.IO.Path.GetFileNameWithoutExtension(ProjectName) + "* - NTime";
			else
				Form.Text = System.IO.Path.GetFileNameWithoutExtension(ProjectName) + " - NTime";
		}

		/// <summary>
		/// Updates status bar with performance test statistics
		/// </summary>
		private void UpdateStatusBar()
		{
			Form.StatusBarPanelInfo.Text = statusInfo;
			Form.StatusBarPanelTotal.Text = "Total Tests: " + totalTests.ToString();
			Form.StatusBarPanelAccepted.Text = "Accepted: " + acceptedTests.ToString();
			Form.StatusBarPanelRejected.Text = "Rejected: " + rejectedTests.ToString();
			Form.StatusBarPanelTime.Text = "Time: " + totalTestsTime.ToString();
		}
		/// <summary>
		/// Checks whether project has changed and needs to be saved.
		/// </summary>
		/// <returns>Returns true when new action is allowed to be done, otherwise must not change project state.</returns>
		private bool CheckDirty()
		{
			if(dirtyProject)
			{
				DialogResult dr = MessageBox.Show("The Project has changed.\n\nDo you want to save the changes?", "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
				if(dr == DialogResult.Yes)
				{
					Save();
					return dirtyProject == false;
				}
				else if(dr == DialogResult.No)
				{
					return true;
				}
				return false;
			}
			return true;
		}

		/// <summary>
		/// Checks whether the form may close. This method is used by Exit menu or Close form event.
		/// </summary>
		/// <returns></returns>
		public bool Close()
		{
			if(CheckDirty())
			{
				return true;
			}
			return false;
		}

		/// <summary>
		/// Creates new empty project.
		/// </summary>
		public void NewProject()
		{
			if(CheckDirty())
			{
				ProjectName = "Untitled.ntime";
				ConfigurationFilename = "";
				AssemblyNames.Clear();
				Reload();
				dirtyProject = false;
				firstUse = true;
				UpdateCaption();
			}
		}

		/// <summary>
		/// Opens dialog to specify filename to load project or assembly with custom tests.
		/// </summary>
		public void Open()
		{
			if(CheckDirty())
			{
				OpenFileDialog openFileDialog = new OpenFileDialog();
				openFileDialog.Filter = "NTime Projects & Assemblies (*.ntime,*.dll,*.exe)|*.ntime;*.dll;*.exe|All Files (*.*)|*.*";
				if(openFileDialog.ShowDialog() == DialogResult.OK)
				{
					string fileName = openFileDialog.FileName;
					if(System.IO.Path.GetExtension(fileName) == ".dll" || System.IO.Path.GetExtension(fileName) == ".exe")
					{
						dirtyProject = false;
						NewProject();
						firstUse = false;
						ProjectName = System.IO.Path.GetDirectoryName(fileName) + "\\" + System.IO.Path.GetFileNameWithoutExtension(fileName) + ".ntime";
						AssemblyNames.Add(fileName);
						ConfigurationFilename = System.IO.Path.GetFileName(fileName) + ".config";
						Reload();
					}
					else if(System.IO.Path.GetExtension(fileName) == ".ntime")
					{
						dirtyProject = false;
						NewProject();
						firstUse = false;
						ProjectName = fileName;
						LoadProject();
					}
					else
						MessageBox.Show("Unknown file format.\nOpen NTime project file or an assembly.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
			}
		}

		/// <summary>
		/// Opens dialog to save project to chosen filename.
		/// </summary>
		public void SaveAs()
		{
			SaveFileDialog saveFileDialog = new SaveFileDialog();
			saveFileDialog.Filter = "NTime Projects (*.ntime)|*.ntime|All Files (*.*)|*.*";
			saveFileDialog.FileName = System.IO.Path.GetFileName(ProjectName);
			if(saveFileDialog.ShowDialog() == DialogResult.OK)
			{
				ProjectName = saveFileDialog.FileName;
				firstUse = false;
				Save();
				dirtyProject = false;
				UpdateCaption();
			}
		}

		/// <summary>
		/// Saves current project.
		/// </summary>
		/// <returns></returns>
		public bool Save()
		{
			if(firstUse)
				SaveAs();
			System.Xml.XmlTextWriter xmlwriter = new System.Xml.XmlTextWriter(ProjectName, System.Text.Encoding.UTF8);
			xmlwriter.Formatting = System.Xml.Formatting.Indented;
			xmlwriter.WriteStartDocument();
			xmlwriter.WriteStartElement("NTimeProject");

			xmlwriter.WriteStartElement("ConfigurationFilename");
			xmlwriter.WriteAttributeString("name", ConfigurationFilename);
			xmlwriter.WriteEndElement();
			
			xmlwriter.WriteStartElement("Assemblies");
			foreach(string s in AssemblyNames)
			{
				xmlwriter.WriteStartElement("Assembly");
				xmlwriter.WriteAttributeString("name", s);
				xmlwriter.WriteEndElement();
			}
			xmlwriter.WriteEndElement(); // Assemblies

			xmlwriter.WriteEndElement(); // NTimeProject
			xmlwriter.WriteEndDocument();
			xmlwriter.Close();

			dirtyProject = false;
			UpdateCaption();
			return true;
		}

		/// <summary>
		/// Loads and parses xml project file.
		/// </summary>
		public void LoadProject()
		{
			System.Xml.XmlTextReader xmlreader = new System.Xml.XmlTextReader(ProjectName);
			xmlreader.MoveToContent();
			while(xmlreader.Read())
			{
				if(xmlreader.NodeType == System.Xml.XmlNodeType.Element)
				{
					switch(xmlreader.Name)
					{
						case "Assembly":
							string assemblyName = xmlreader.GetAttribute("name");
                            AssemblyNames.Add(assemblyName);
							break;
						case "ConfigurationFilename":
							ConfigurationFilename = xmlreader.GetAttribute("name");
							break;
					}
				}
			}
			Reload();
		}

		/// <summary>
		/// Runs loaded and selected tests.
		/// </summary>
		public void Run()
		{
			Reload();
			Running = true;
			ResetNodesArray(Form.TreeView.Nodes[0]);
			Form.toolBarRun.Enabled = false;
			Form.menuRun.Enabled = false;
			Form.toolBarStop.Enabled = true;
			Form.menuStop.Enabled = true;
			Form.toolBarNewProject.Enabled = false;
			Form.menuNewProject.Enabled = false;
			Form.toolBarOpenProject.Enabled = false;
			Form.menuOpen.Enabled = false;
			Form.menuAddAssembly.Enabled = false;
			Form.menuReload.Enabled = false;
			Form.menuRecent.Enabled = false;
			acceptedTests = 0;
			rejectedTests = 0;
			totalTestsTime = TimeSpan.Zero;
			statusInfo = "Running";
			UpdateStatusBar();
			ArrayList a = new ArrayList();
			if(Form.TreeView.SelectedNode != null)
			{
				BuildNodesArray(Form.TreeView.SelectedNode, ref a);
				if(a.Count > 0)
				{
					rl.RunUnit(a);
					Form.TimerQuery.Enabled = true;
				}
			}
		}

		/// <summary>
		/// Queries for finished tests to update treenodes and gui.
		/// This method is executed in specified time periods.
		/// </summary>
		public void QueryTests()
		{
			TestUnit tu;
			do
			{
				tu = rl.QueryUnit(false);
				if(tu != null)
				{
					if(tu.Result == UnitResult.Accepted)
					{
						tu.Node.ImageIndex = 1;
						tu.Node.SelectedImageIndex = 1;
						acceptedTests++;
					}
					else if(tu.Result == UnitResult.Rejected)
					{
						tu.Node.ImageIndex = 2;
						tu.Node.SelectedImageIndex = 2;
						rejectedTests++;
					}
					else if(tu.Result == UnitResult.Ignored)
					{
						tu.Node.ImageIndex = 3;
						tu.Node.SelectedImageIndex = 3;
					}
					UpdateStatusBar();
				}
				else
				{
					lock(rl)
					{
						if(rl.TestsFinished)
						{
							totalTestsTime = rl.TotalTime;
							UpdateStatusBar();
							Form.TimerQuery.Enabled = false;
							Running = false;
							Form.toolBarRun.Enabled = true;
							Form.menuRun.Enabled = true;
							Form.toolBarStop.Enabled = false;
							Form.menuStop.Enabled = false;
							Form.toolBarNewProject.Enabled = true;
							Form.menuNewProject.Enabled = true;
							Form.toolBarOpenProject.Enabled = true;
							Form.menuOpen.Enabled = true;
							Form.menuAddAssembly.Enabled = true;
							Form.menuReload.Enabled = true;
							// TODO: gdy bedzie gotowy recent menu to tutaj robic true
//							Form.menuRecent.Enabled = true;

							statusInfo = "Ready";
							UpdateStatusBar();
							if(Form.TreeView.SelectedNode != null)
							{
								ShowNodeInfo();
							}
							break;
						}
					}
				}
			} while(tu != null);
		}

		/// <summary>
		/// Fills recursive an array with selected tests in TreeView.
		/// </summary>
		/// <param name="t">Start node.</param>
		/// <param name="array">Array to fill in.</param>
		private void BuildNodesArray(TreeNode t, ref ArrayList array)
		{
			if(t.Tag != null)
			{
				((TestUnit)t.Tag).Node = t;
				array.Add(t.Tag);
			}
			foreach(TreeNode t2 in t.Nodes)
			{
				BuildNodesArray(t2, ref array);
			}
		}

		/// <summary>
		/// Resets tree node icon to default state. Used when starting tests.
		/// </summary>
		/// <param name="t">Start node.</param>
		private void ResetNodesArray(TreeNode t)
		{
			if(t.Nodes.Count == 0)
			{
				t.ImageIndex = 0;
				t.SelectedImageIndex = 0;
			}
			else
			{
				t.ImageIndex = 4;
				t.SelectedImageIndex = 4;
			}
			foreach(TreeNode t2 in t.Nodes)
			{
				ResetNodesArray(t2);
			}
		}

		/// <summary>
		/// Stops running tests.
		/// </summary>
		public void Stop()
		{
			if(Running)
			{
				statusInfo = "Stopping";
				UpdateStatusBar();
				Form.toolBarRun.Enabled = false;
				Form.menuRun.Enabled = false;
				Form.toolBarStop.Enabled = false;
				Form.menuStop.Enabled = false;
				rl.QueryUnit(true);
			}
		}


		/// <summary>
		/// Opens options dialog box.
		/// </summary>
		public void Options()
		{
			OptionsDialog optionsDialog = new OptionsDialog();
			optionsDialog.ConfigFilename.Text = ConfigurationFilename;
			if(DialogResult.OK == optionsDialog.ShowDialog())
			{
				ConfigurationFilename = optionsDialog.ConfigFilename.Text;
				dirtyProject = true;
				UpdateCaption();
				Reload();
			}
		}

		/// <summary>
		/// Places summary test text in edit box right to TreeView.
		/// </summary>
		public void ShowNodeInfo()
		{
			Form.NodeInfo.Text = "";
			ArrayList array = new ArrayList();
			BuildNodesArray(Form.TreeView.SelectedNode, ref array);
			ArrayList desc = new ArrayList();

			foreach(TestUnit tu in array)
			{
				desc.InsertRange(desc.Count, tu.UnitResultInfo);
			}
			if(desc.Count > 0)
				Form.NodeInfo.Lines = (string[])desc.ToArray(typeof(string));
		}

		/// <summary>
		/// Reloads assemblies. This function is used often for refreshing program state.
		/// </summary>
		public void Reload()
		{
			statusInfo = "Loading";
			UpdateStatusBar();
			lock(this)
			{
				if(rl != null)
					Domain.DestroyDomain();
				string[] st = (string[])AssemblyNames.ToArray(typeof(string));
				rl = Domain.CreateDomain(st, ConfigurationFilename);

				UpdateCaption();
				Form.TreeView.Nodes.Clear();
				TreeNode n = Form.TreeView.Nodes.Add("Performance Tests");
				n.ImageIndex = 4;
				n.SelectedImageIndex = 4;
				Form.menuReload.Enabled = AssemblyNames.Count > 0;

				foreach(string s in AssemblyNames)
				{
					rl.LoadAssembly(s);
				}
				PopulateTree();

				directoryWatcher.Clear();
				for(int i = 0; i < AssemblyNames.Count; i++)
				{
					directoryWatcher.Add(new System.IO.FileSystemWatcher(System.IO.Path.GetDirectoryName(AssemblyNames[i].ToString()), "*.*"));
					((System.IO.FileSystemWatcher)directoryWatcher[i]).Created += new System.IO.FileSystemEventHandler(onFileChange);
					((System.IO.FileSystemWatcher)directoryWatcher[i]).Changed += new System.IO.FileSystemEventHandler(onFileChange);
					((System.IO.FileSystemWatcher)directoryWatcher[i]).Deleted += new System.IO.FileSystemEventHandler(onFileChange);
					((System.IO.FileSystemWatcher)directoryWatcher[i]).EnableRaisingEvents = true;
				}
				statusInfo = "Ready";
				UpdateStatusBar();
				Form.TreeView.SelectedNode = Form.TreeView.Nodes[0];
				//			Domain.DestroyDomain();
			}
		}

		/// <summary>
		/// Fired when file watcher finds any change in directory.
		/// </summary>
		/// <param name="obj">File watcher object.</param>
		/// <param name="e">File watcher event arguments.</param>
		private void onFileChange(object obj, System.IO.FileSystemEventArgs e)
		{
			foreach(string s in AssemblyNames)
			{
				if(s.ToLower() == e.FullPath.ToLower())
				{
					if(Running == false)
						Form.TimerFile.Enabled = true;
					break;
				}
			}
		}

		/// <summary>
		/// Invokes assembly editor to add or remove assemblies with tests.
		/// </summary>
		public void AddAssembly()
		{
			AddAssemblyForm form = new AddAssemblyForm();
			form.listBox.Items.Clear();
			foreach(string s in AssemblyNames)
			{
				form.listBox.Items.Add(s);
			}
			if(form.ShowDialog() == DialogResult.OK)
			{
				dirtyProject = true;
				AssemblyNames.Clear();
				foreach(string s in form.listBox.Items)
				{
					AssemblyNames.Add(s);
				}
				UpdateCaption();
				Reload();
			}
		}

		/// <summary>
		/// Populates tree with loaded tests in assemblies.
		/// </summary>
		private void PopulateTree()
		{
			Form.menuExpand.Enabled = false;
			Form.menuExpandAll.Enabled = false;
			Form.menuCollapse.Enabled = false;
			Form.menuCollapseAll.Enabled = false;
			Form.menuRun.Enabled = false;
			Form.toolBarRun.Enabled = false;
			totalTests = 0;
			totalTestsTime = TimeSpan.Zero;
			acceptedTests = 0;
			rejectedTests = 0;
			if(rl != null)
			{
				foreach(TestFixture tf in rl.Fixtures)
				{
					foreach(TestUnit tu in tf.Units)
					{
						AddToTree(tu.ObjectName.ToString(), tu);
						totalTests++;
					}
				}
			}
			Form.TreeView.ExpandAll();
		}

		/// <summary>
		/// Adds node to treeview control.
		/// </summary>
		/// <param name="typeName">.net type (with namespace) to add to treeview.</param>
		/// <param name="testUnit">TestUnit instance to add to node tag for its further reference.</param>
		private void AddToTree(string typeName, TestUnit testUnit)
		{
			string[] ns = typeName.Split('.', '+');
			TreeNode n = Form.TreeView.Nodes[0];
			foreach(string s in ns)
			{
				bool found = false;
				foreach(TreeNode t in n.Nodes)
				{
					if(t.Text == s)
					{
						found = true;
						n = t;
						break;
					}
				}
				if(found == false)
				{
					if(s == ns[ns.Length - 1])
						n = n.Nodes.Add(s + " (" + testUnit.TypeName + ")");
					else
						n = n.Nodes.Add(s);
					n.ImageIndex = 4;
					n.SelectedImageIndex = 4;
					if(s == ns[ns.Length - 1]) // TODO: tu jest chyba blad gdy klasa i metoda maja te same nazwy
					{
						n.Tag = testUnit;
						n.ImageIndex = 0;
						n.SelectedImageIndex = 0;
					}
				}
			}
			Form.menuExpand.Enabled = true;
			Form.menuExpandAll.Enabled = true;
			Form.menuCollapse.Enabled = true;
			Form.menuCollapseAll.Enabled = true;
			Form.menuRun.Enabled = true;
			Form.toolBarRun.Enabled = true;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Web Developer
Poland Poland
Born in Poland, living there as employeed developer, in free time writing much .net stuff and designing applications.

Comments and Discussions