Edison.GUI/Form1.cs
/*
Edison is designed to be simpler and more performant unit/integration testing framework.
Copyright (c) 2015, Matthew Kelly (Badgerati)
Company: Cadaeic Studios
License: MIT (see LICENSE for details)
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Reflection;
using System.IO;
using Edison.Engine.Contexts;
using Edison.Engine.Core.Enums;
using Edison.Engine;
using System.Threading;
using Edison.Framework;
using Edison.Engine.Repositories.Interfaces;
using Edison.Injector;
using Edison.Engine.Core.Exceptions;
namespace Edison.GUI
{
public partial class EdisonForm : Form
{
#region Repositories
private IAssemblyRepository AssemblyRepository
{
get { return DIContainer.Instance.Get<IAssemblyRepository>(); }
}
private IPathRepository PathRepository
{
get { return DIContainer.Instance.Get<IPathRepository>(); }
}
private IReflectionRepository ReflectionRepository
{
get { return DIContainer.Instance.Get<IReflectionRepository>(); }
}
private IFileRepository FileRepository
{
get { return DIContainer.Instance.Get<IFileRepository>(); }
}
#endregion
#region Properties
public const string MainTitle = "Edison";
public const char Separator = '.';
private Assembly Assembly = default(Assembly);
private int TotalNumberOfTestsRunning = 0;
private int CurrentNumberOfTestsRun = 0;
private int CurrentNumberOfFailedTests = 0;
private int CurrentNumberOfErroredTests = 0;
private string FileName = string.Empty;
private string FilePath = string.Empty;
private List<string> CheckedTests = new List<string>();
private List<string> CheckedFixtures = new List<string>();
private EdisonContext EdisonContext = default(EdisonContext);
private Thread MainThread = default(Thread);
#endregion
#region Constructor
public EdisonForm()
{
InitializeComponent();
RefreshRecentlyOpened();
FormClosing += EdisonForm_FormClosing;
TestTree.NodeMouseDoubleClick += TestTree_NodeMouseDoubleClick;
TestTree.KeyDown += TestTree_KeyDown;
FailedTestListBox.MeasureItem += FailedTestListBox_MeasureItem;
FailedTestListBox.DrawItem += FailedTestListBox_DrawItem;
SuiteCheckList.ItemCheck += SuiteCheckList_ItemCheck;
RecentlyOpenedMenuItem.DropDownItemClicked += RecentlyOpenedMenuItem_DropDownItemClicked;
FixtureThreadNumericBox.Value = 1;
TestThreadNumericBox.Value = 1;
TestProgressBar.Value = 0;
}
#endregion
#region Events
private void EdisonForm_FormClosing(object sender, FormClosingEventArgs e)
{
var abortThread = new Thread(() => AbortThreads());
abortThread.Start();
}
private void label2_Click(object sender, EventArgs e)
{
DisableConsoleCheckBox.Checked = !DisableConsoleCheckBox.Checked;
}
private void label3_Click(object sender, EventArgs e)
{
DisableTestCheckBox.Checked = !DisableTestCheckBox.Checked;
}
private void FailedTestListBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0)
{
return;
}
e.DrawBackground();
var item = (TestResult)(FailedTestListBox.Items[e.Index]);
e.Graphics.DrawString(item.FullName + Environment.NewLine + item.ErrorMessage, e.Font, new SolidBrush(e.ForeColor), e.Bounds);
}
private void FailedTestListBox_MeasureItem(object sender, MeasureItemEventArgs e)
{
e.ItemHeight = 65;
}
private void FailedTestListBox_SelectedIndexChanged(object sender, EventArgs e)
{
FailedTestDetails.Clear();
FailedTestDetails.AppendText("StackTrace:" + Environment.NewLine + ((TestResult)(((ListBox)sender).SelectedItem)).StackTrace);
}
private void expandToolStripMenuItem_Click(object sender, EventArgs e)
{
TestTree.ExpandAll();
if (TestTree.Nodes.Count > 0)
{
TestTree.Nodes[0].EnsureVisible();
}
}
private void collapseToolStripMenuItem_Click(object sender, EventArgs e)
{
TestTree.CollapseAll();
}
private void checkboxesToolStripMenuItem_Click(object sender, EventArgs e)
{
TestTree.CheckBoxes = !TestTree.CheckBoxes;
}
private void uncheckAllToolStripMenuItem_Click(object sender, EventArgs e)
{
ToggleCheckBoxes(TestTree.Nodes, false);
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
var dialog = new OpenFileDialog()
{
CheckFileExists = true,
CheckPathExists = true,
DefaultExt = "*.dll",
Multiselect = false,
Title = "Edison - Select a DLL",
Filter = "dll (*.dll)|*.dll"
};
var result = dialog.ShowDialog();
if (result == DialogResult.OK)
{
OpenFile(dialog.FileName);
}
else if (result != DialogResult.Cancel)
{
MessageBox.Show("Failed to open the selected file.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(OutputRichText.Text))
{
MessageBox.Show("Nothing to save.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var dialog = new SaveFileDialog()
{
CheckPathExists = true,
DefaultExt = "*.txt",
Title = "Edison - Save Test Result Output",
Filter = "txt (*.txt)|*.txt"
};
var result = dialog.ShowDialog();
if (result == DialogResult.OK)
{
File.WriteAllText(dialog.FileName, OutputRichText.Text);
}
else if (result != DialogResult.Cancel)
{
MessageBox.Show("Failed to save test result output.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
MessageBox.Show("Author: Matthew Kelly\nVersion: " + Logger.Instance.GetVersion(),
"Edison",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private void TestTree_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
DoTestTreeSelect(e.Node);
}
private void TestTree_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
DoTestTreeSelect(TestTree.SelectedNode);
}
}
private void TestTree_AfterCheck(object sender, TreeViewEventArgs e)
{
var node = e.Node;
if (node.Checked)
{
var tests = node.GetFullPaths(FileName, Separator);
CheckedTests.AddRange(tests.Item1);
CheckedFixtures.AddRange(tests.Item2);
}
else
{
var tests = node.GetFullPaths(FileName, Separator);
foreach (var test in tests.Item1)
{
CheckedTests.Remove(test);
}
foreach (var fixture in tests.Item2)
{
CheckedFixtures.Remove(fixture);
}
}
}
private void RunTestsButton_Click(object sender, EventArgs e)
{
switch (RunTestsButton.Text.ToLower())
{
case "run":
if (TestTree.Nodes.Count != 0)
{
DoTestTreeSelect(null);
}
break;
case "stop":
RunTestsButton.Text = "Run";
var abortThread = new Thread(() => AbortThreads());
abortThread.Start();
break;
}
}
private void tabControl1_Selected(object sender, TabControlEventArgs e)
{
switch (e.TabPageIndex)
{
case 0:
OutputRichText.Focus();
break;
case 1:
FailedTestListBox.Focus();
break;
}
}
private void EdisonContext_OnTestResult(TestResult result)
{
CurrentNumberOfTestsRun++;
AddFailedTestResult(result);
UpdateProgress();
}
private void SuiteCheckList_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (e.NewValue != CheckState.Checked)
{
return;
}
var selectedItems = SuiteCheckList.CheckedIndices;
if (selectedItems.Count > 0)
{
SuiteCheckList.SetItemCheckState(selectedItems[0], CheckState.Unchecked);
}
}
private void RecentlyOpenedMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
var name = e.ClickedItem.Text;
if (string.IsNullOrWhiteSpace(name))
{
return;
}
if (name.Equals("Clear Entries", StringComparison.InvariantCultureIgnoreCase))
{
RecentlyOpened.Clear();
RefreshRecentlyOpened();
}
else if (FileRepository.Exists(name))
{
OpenFile(name);
}
}
#endregion
#region Helpers
private void OpenFile(string path)
{
if (!FileRepository.Exists(path))
{
MessageBox.Show(string.Format("File doesn't exist: '{0}'", path), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
RecentlyOpened.Add(path);
RefreshRecentlyOpened();
FilePath = path;
FileName = PathRepository.GetFileName(FilePath);
Text = MainTitle + " - " + FileName;
Assembly = AssemblyRepository.LoadFile(FilePath);
PopulateForm();
}
private void RunTests(List<string> tests, List<string> fixtures)
{
if (Assembly == default(Assembly) || (MainThread != default(Thread) && MainThread.ThreadState == ThreadState.Running))
{
return;
}
OutputRichText.Clear();
TestProgressBar.Value = 0;
FailedTestListBox.Items.Clear();
FailedTestDetails.Clear();
SetupContext(tests, fixtures);
var logger = new OutputLogger(OutputRichText);
Logger.Instance.SetOutput(logger);
Logger.Instance.SetConsoleOutput(logger);
CurrentNumberOfTestsRun = 0;
CurrentNumberOfErroredTests = 0;
CurrentNumberOfFailedTests = 0;
TotalNumberOfTestsRunning = AssemblyRepository.GetTestCount(
Assembly,
EdisonContext.IncludedCategories,
EdisonContext.ExcludedCategories,
EdisonContext.Fixtures,
EdisonContext.Tests,
EdisonContext.Suite);
MainThread = new Thread(() => Run(EdisonContext));
MainThread.Start();
OutputRichText.Focus();
RunTestsButton.Text = "Stop";
}
private void Run(EdisonContext context)
{
try
{
context.Run();
}
catch (ValidationException vex)
{
MessageBox.Show(vex.Message, "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("{0}{2}{2}{1}", ex.Message, ex.StackTrace, Environment.NewLine), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
SetRunTestButton("Run");
SetProgress(100);
}
private void UpdateProgress()
{
if (CurrentNumberOfTestsRun == 0 || TotalNumberOfTestsRunning == 0)
{
return;
}
var progress = (int)(((double)CurrentNumberOfTestsRun / (double)TotalNumberOfTestsRunning) * 100.0);
SetProgress(Math.Max(0, Math.Min(97, progress)));
}
private void SetProgress(int progress)
{
if (TestProgressBar.IsDisposed)
{
return;
}
try
{
if (TestProgressBar.InvokeRequired)
{
TestProgressBar.Invoke((MethodInvoker)delegate {
TestProgressBar.Value = progress;
TestProgressBar.ForeColor = CurrentNumberOfErroredTests > 0
? Color.Red
: CurrentNumberOfFailedTests > 0
? Color.Orange
: Color.LimeGreen;
TestProgressBar.BackColor = TestProgressBar.ForeColor;
});
}
else
{
TestProgressBar.Value = progress;
TestProgressBar.ForeColor = CurrentNumberOfErroredTests > 0
? Color.Red
: CurrentNumberOfFailedTests > 0
? Color.Orange
: Color.LimeGreen;
TestProgressBar.BackColor = TestProgressBar.ForeColor;
}
}
catch (ObjectDisposedException) { }
}
private void AddFailedTestResult(TestResult result)
{
if (result.State == TestResultState.Success)
{
return;
}
if (FailedTestListBox.IsDisposed)
{
return;
}
if (result.IsFailure)
{
CurrentNumberOfFailedTests++;
}
else if (result.IsError)
{
CurrentNumberOfErroredTests++;
}
try
{
if (FailedTestListBox.InvokeRequired)
{
FailedTestListBox.BeginInvoke((MethodInvoker)delegate { FailedTestListBox.Items.Add(result); });
}
else
{
FailedTestListBox.Items.Add(result);
}
}
catch (ObjectDisposedException) { }
}
private void SetRunTestButton(string value)
{
if (RunTestsButton.IsDisposed)
{
return;
}
try
{
if (RunTestsButton.InvokeRequired)
{
RunTestsButton.BeginInvoke((MethodInvoker)delegate { RunTestsButton.Text = value; });
}
else
{
RunTestsButton.Text = value;
}
}
catch (ObjectDisposedException) { }
}
private void SetupContext(List<string> tests, List<string> fixtures)
{
EdisonContext = EdisonContext.Create();
EdisonContext.Assemblies.Add(FilePath);
EdisonContext.NumberOfFixtureThreads = (int)FixtureThreadNumericBox.Value;
EdisonContext.NumberOfTestThreads = (int)TestThreadNumericBox.Value;
EdisonContext.DisableConsoleOutput = DisableConsoleCheckBox.Checked;
EdisonContext.DisableTestOutput = DisableTestCheckBox.Checked;
EdisonContext.IncludedCategories.AddRange(IncludedCategoriesCheckList.CheckedItems.Cast<string>());
EdisonContext.ExcludedCategories.AddRange(ExcludedCategoriesCheckList.CheckedItems.Cast<string>());
EdisonContext.Suite = SuiteCheckList.CheckedItems.Cast<string>().FirstOrDefault();
if (tests != default(List<string>))
{
EdisonContext.Tests.AddRange(tests);
}
if (fixtures != default(List<string>))
{
EdisonContext.Fixtures.AddRange(fixtures);
}
EdisonContext.DisableFileOutput = true;
EdisonContext.ConsoleOutputType = OutputType.Txt;
EdisonContext.OnTestResult += EdisonContext_OnTestResult;
}
private void DoTestTreeSelect(TreeNode node)
{
if (node == default(TreeNode))
{
if (CheckedTests.Any() || CheckedFixtures.Any())
{
RunTests(CheckedTests, CheckedFixtures);
return;
}
node = TestTree.SelectedNode;
}
if (node != default(TreeNode))
{
var tests = node.GetFullPaths(FileName, Separator);
RunTests(tests.Item1, tests.Item2);
}
}
private void ToggleCheckBoxes(TreeNodeCollection nodes, bool flag)
{
foreach (TreeNode node in nodes)
{
ToggleCheckBoxes(node.Nodes, flag);
node.Checked = flag;
}
}
private void PopulateForm()
{
TestTree.Nodes.Clear();
IncludedCategoriesCheckList.Items.Clear();
ExcludedCategoriesCheckList.Items.Clear();
SuiteCheckList.Items.Clear();
var tests = AssemblyRepository.GetTests(Assembly, default(IList<string>), default(IList<string>), default(IList<string>), default(IList<string>), null);
PopulateTests(tests.Item1);
var categories = AssemblyRepository.GetCategories(Assembly, tests.Item1, tests.Item2);
PopulateCategories(categories);
var suites = AssemblyRepository.GetSuites(Assembly, tests.Item2);
PopulateSuites(suites);
}
private void PopulateSuites(IEnumerable<string> suites)
{
if (!suites.Any())
{
return;
}
foreach (var suite in suites)
{
SuiteCheckList.Items.Add(suite, CheckState.Unchecked);
}
}
private void PopulateCategories(IEnumerable<string> categories)
{
if (!categories.Any())
{
return;
}
foreach (var category in categories)
{
IncludedCategoriesCheckList.Items.Add(category, CheckState.Unchecked);
ExcludedCategoriesCheckList.Items.Add(category, CheckState.Unchecked);
}
}
private void PopulateTests(IEnumerable<MethodInfo> tests)
{
var rootNode = new TreeNode(FileName);
foreach (var test in tests)
{
var currentNode = rootNode;
var pathItems = ReflectionRepository.GetFullNamespace(test).Split(Separator);
foreach (var item in pathItems)
{
var temp = currentNode.Nodes.Cast<TreeNode>().Where(x => x.Text.Equals(item));
currentNode = temp.Any() ? temp.Single() : currentNode.Nodes.Add(item);
}
}
TestTree.Nodes.Add(rootNode);
TestTree.PathSeparator = Separator.ToString();
TestTree.ContextMenuStrip = TestTreeContextMenu;
TestTree.Focus();
}
private void AbortThreads()
{
if (MainThread != default(Thread))
{
MainThread.Abort();
}
if (EdisonContext != default(EdisonContext) && EdisonContext.IsRunning)
{
EdisonContext.Interrupt();
}
}
private void RefreshRecentlyOpened()
{
RecentlyOpened.Populate(RecentlyOpenedMenuItem);
}
#endregion
}
}