xenadmin/XenAdminTests/MainWindowTester.cs

520 lines
20 KiB
C#
Raw Normal View History

/* Copyright (c) Citrix Systems, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using NUnit.Framework;
using XenAdmin;
using XenAdmin.Controls;
using XenAdmin.Controls.MainWindowControls;
using XenAdmin.Core;
using XenAdmin.Model;
using XenAdmin.Network;
using XenAPI;
namespace XenAdminTests
{
public abstract class MainWindowTester : TestObjectProvider
{
// Run on MainWindowThread
private static object[] EMPTY = new object[0];
protected void MW(MethodInvoker f)
{
MW(f, EMPTY);
}
protected T MW<T>(Func<T> func)
{
return (T)MW(func, EMPTY);
}
protected object MW(Delegate f, params object[] args)
{
Assert.IsNotNull(Program.MainWindow, "Program.MainWindow was found to be null");
Assert.IsNotNull(f, "Delegate was found to be null");
Assert.IsNotNull(args, "Params were found to be null");
try
{
DoEvents();
object o = Program.Invoke(Program.MainWindow, f, args);
DoEvents();
if (Program.TestExceptionString != null)
throw new Exception(Program.TestExceptionString);
return o;
}
catch (Exception e)
{
String message = Program.TestExceptionString ?? e.GetBaseException().ToString();
Program.TestExceptionString = null;
Assert.Fail(message);
return null;
}
}
/// <summary>
/// Waits for the specified action to return true. The action is run on the main program thread.
/// The action is attempted 300 times, with a 100ms wait between tries.
/// If the action has not succeeeded after the retires, the calling test fails with the specified message.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="assertMessage">The assert message.</param>
/// <returns>A value indicating whether the action was successful.</returns>
protected bool MWWaitFor(Func<bool> action, string assertMessage = null)
{
bool success = false;
for (int i = 0; i < 500; i++)
{
success = MW(action);
if (success)
break;
Thread.Sleep(300);
}
if (!success)
Assert.Fail(string.IsNullOrEmpty(assertMessage) ? "Waited unsuccessfully" : assertMessage);
return success;
}
/// <summary>
/// Waits for the specified action to return true. The action is run on the calling thread.
/// The action is attempted 300 times, with a 100ms wait between tries.
/// If the action has not succeeded after the retries, the calling test fails with the specified message.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="assertMessage">The assert message.</param>
/// <returns>A value indicating whether the action was successful.</returns>
protected void WaitFor(Func<bool> action, string assertMessage = null)
{
bool success = false;
for (int i = 0; i < 300; i++)
{
success = action();
if (success)
break;
Thread.Sleep(100);
}
if (!success)
Assert.Fail(string.IsNullOrEmpty(assertMessage) ? "Waited unsuccessfully" : assertMessage);
}
internal Win32Window WaitForWindowToAppear(string windowText, Predicate<Win32Window> match = null)
{
if (match == null)
match = w => true;
Win32Window window = null;
Func<bool> func = delegate
{
window = Win32Window.GetWindowWithText(windowText, match);
return window != null;
};
WaitFor(func, $"Window with text {windowText} didn't appear.");
return window;
}
/// <summary>
/// <see cref="Application.DoEvents()"/> doesn't seems to work from the test framework. This is a replacement.
/// </summary>
private void DoEvents()
{
if (Program.MainWindow.InvokeRequired)
{
bool messagesPending = true;
while (messagesPending)
{
Program.Invoke(Program.MainWindow, delegate
{
foreach (Win32Window w in Win32Window.GetThreadWindows())
{
messagesPending = w.MessagesPending;
if (messagesPending)
{
break;
}
}
});
if (messagesPending)
{
Thread.Sleep(5);
}
}
}
}
// Put the TestResources directory on the front of a filename
protected string TestResource(string name)
{
return GetTestResource(name);
}
public static string GetTestResource(string name)
{
Integrade changes from master-7.4 into master (https://github.com/xcp-ng/xenadmin/issues/68) proper fix: use Path to TestDirectory https://github.com/nunit/docs/wiki/Breaking-Changes CurrentDirectory: No longer set to the directory containing the test assembly. Use TestContext.CurrentContext.TestDirectory to locate that directory. fix Test CertificateDialogTests Path to the Cert was build as: xenadmin\TestResources\TestDevCertificate.cer but should be: xenadmin\XenAdminTests\TestResources\TestDevCertificate.cer fix Test TestFailingReaderGeneration Ionic.BZip2 is throwing an IOException: System.IO.IOException: 'Not a valid BZip2 stream. byte 0, expected '66', got '-1'' use update.xml in master brach on github (as a first solution) https://github.com/xcp-ng/xenadmin/issues/29 Do not check for ContainerCapability on XCP-ng, just belive in it :-) If server is in not licenced, don't show alert icon Changed SetUp to OneTimeSetup in SetUpFixture https://github.com/xcp-ng/xenadmin/issues/24 abort, if BUILD_NUMBER environment variable is not set build automation Ignore ".vs" folder splash buildable with Visual Studio Community 2018 https://github.com/xcp-ng/xenadmin/issues/24 Get Tests running https://github.com/xcp-ng/xenadmin/issues/24 Get Tests running real world: edition "platinum" has changed to "desktop-plus" https://github.com/xcp-ng/xenadmin/issues/24 Get Tests running set culture to "en-EN" to ensure correct parsing https://github.com/xcp-ng/xenadmin/issues/24 Get Tests running correct adaption to nunit 3 adapt to nunit 3 adapt path real world: edition "platinum" has changed to "desktop-plus" Use Nunit and Moq from NuGet; Adapt tests to Nunit 3 https://github.com/xcp-ng/xenadmin/issues/24 Get Tests running Fill AssemblyFileVersion Idea from https://github.com/xcp-ng/xenadmin/issues/17 Show Version in Title Tidy up Branding/Trademarks/License https://github.com/xcp-ng/xenadmin/issues/18 Comment unreachable code to be buildable (https://github.com/xcp-ng/xenadmin/issues/14#issuecomment-380041068) XCP-ng Console should connect to any flavor of XenServer (cherry picked from commit 7e2342534d75193685139226fcaf89bb365fc706)
2018-04-10 00:46:52 +02:00
return Path.Combine(Directory.GetCurrentDirectory(), @"XenAdminTests\TestResources", name);
}
protected List<VirtualTreeNode> GetAllTreeNodes()
{
return MW(() => new List<VirtualTreeNode>(MainWindowWrapper.TreeView.AllNodes));
}
protected void EnsureChecked(ToolStripMenuItem menuItem)
{
EnsureChecked(menuItem, CheckState.Checked);
}
protected void EnsureChecked(ToolStripMenuItem menuItem, CheckState checkState)
{
if (menuItem.CheckState != checkState)
MW(menuItem.PerformClick);
}
protected void EnsureDefaultTemplatesShown()
{
EnsureChecked(MainWindowWrapper.ViewMenuItems.TemplatesToolStripMenuItem);
MWWaitFor(delegate
{
var nodes = new List<VirtualTreeNode>(MainWindowWrapper.TreeView.AllNodes);
var defaultTemplates = nodes.Select(n => n.Tag is VM && ((VM)n.Tag).DefaultTemplate());
return (defaultTemplates.Count() > 0);
});
}
protected void ClickMenuItem(string menuName, string itemName)
{
MW(delegate()
{
ToolStripItem item = GetMenuItem(menuName, itemName);
Assert.NotNull(item, string.Format("Menu item '{0}' not found under menu '{1}'", itemName, menuName));
item.PerformClick();
});
}
protected void CheckMenuItemMissing(string menuName, string itemName)
{
MW(delegate()
{
ToolStripItem item = GetMenuItem(menuName, itemName);
Assert.Null(item, string.Format("Did not expect to find menu item '{0}' under menu '{1}'", itemName, menuName));
});
}
/// <summary>
/// May return null
/// </summary>
private ToolStripItem GetMenuItem(string menu, string item)
{
ToolStripMenuItem menuItem = null;
foreach (ToolStripMenuItem m in MainWindowWrapper.MainMenuBar.Items)
{
if (m.Text.Replace("&", "") == menu)
{
menuItem = m;
break;
}
}
if (menuItem == null)
return null;
foreach (ToolStripItem i in menuItem.DropDownItems)
{
if (i.Text.Replace("&", "") == item)
return i;
}
return null;
}
/// <summary>
/// Selects the specified IXenObjects or GroupingTags in the tree.
/// </summary>
/// <param name="ixmos">The IXenObjects or GroupingTags to select.</param>
/// <returns>A value indicating whether the action was successful.</returns>
protected bool SelectInTree(params object[] ixmos)
{
return SelectInTree(vm => true, ixmos);
}
/// <summary>
/// Selects the specified IXenObjects or GroupingTags in the tree.
/// </summary>
/// <param name="match">The condition that the node must match.</param>
/// <param name="ixmos">The IXenObjects or GroupingTags to select.</param>
/// <returns>A value indicating whether the action was successful.</returns>
protected bool SelectInTree(Predicate<VirtualTreeNode> match, params object[] ixmos)
{
return MWWaitFor(() =>
{
List<VirtualTreeNode> nodes = new List<VirtualTreeNode>();
foreach (object x in ixmos)
{
VirtualTreeNode node = FindInTree(x, match);
if (node == null)
return false;
nodes.Add(node);
}
nodes.ForEach(v => v.EnsureVisible());
MainWindowWrapper.TreeView.SelectedNodes.SetContents(nodes);
return true;
});
}
/// <summary>
/// Finds the first (there maybe more than one) node that has specified IXenObject or GroupingTag as its tag.
/// Returns null if the object isn't found.
/// </summary>
/// <param name="ixmo">The IXenObject or GroupingTag to be found.</param>
/// <returns>The node.</returns>
protected VirtualTreeNode FindInTree(object ixmo)
{
return FindInTree(ixmo, v => true);
}
/// <summary>
/// Finds the first (there maybe more than one) node that has specified IXenObject or GroupingTag as its tag
/// that matches the specified condition. Returns null if the object isn't found.
/// </summary>
/// <param name="ixmo">The IXenObject or GroupingTag to be found.</param>
/// <param name="node">The condition that the node must match.</param>
/// <returns>The node.</returns>
protected VirtualTreeNode FindInTree(object ixmo, Predicate<VirtualTreeNode> match)
{
foreach (VirtualTreeNode node in MainWindowWrapper.TreeView.AllNodes)
{
bool found = false;
if (ixmo == null && node.Tag == null)
{
found = true;
}
else
{
Folder f = ixmo as Folder;
Folder ff = node.Tag as Folder;
// special case for folders as Folder.Equals is broken...
if (f != null && ff != null && f.opaque_ref == ff.opaque_ref)
{
found = true;
}
else if (ixmo != null && ixmo.Equals(node.Tag))
{
found = true;
}
}
if (found && match(node))
{
return node;
}
}
return null;
}
/// <summary>
/// Puts the main treeview into Infrastructure, Objects or Organization
/// view and waits until the tree has been repopulated.
/// </summary>
protected void PutInNavigationMode(NavigationPane.NavigationMode mode)
{
MW(() =>
{
var originalMode = GetNavigationMode();
switch (mode)
{
case NavigationPane.NavigationMode.Infrastructure:
TestUtils.GetToolStripItem(MainWindowWrapper.Item, "navigationPane.buttonInfraBig").PerformClick();
break;
case NavigationPane.NavigationMode.Objects:
TestUtils.GetToolStripItem(MainWindowWrapper.Item, "navigationPane.buttonObjectsBig").PerformClick();
break;
case NavigationPane.NavigationMode.Tags:
TestUtils.GetToolStripMenuItem(MainWindowWrapper.Item, "navigationPane.toolStripMenuItemTags").PerformClick();
break;
case NavigationPane.NavigationMode.Folders:
TestUtils.GetToolStripMenuItem(MainWindowWrapper.Item, "navigationPane.toolStripMenuItemFolders").PerformClick();
break;
case NavigationPane.NavigationMode.CustomFields:
TestUtils.GetToolStripMenuItem(MainWindowWrapper.Item, "navigationPane.toolStripMenuItemFields").PerformClick();
break;
case NavigationPane.NavigationMode.vApps:
TestUtils.GetToolStripMenuItem(MainWindowWrapper.Item, "navigationPane.toolStripMenuItemVapps").PerformClick();
break;
case NavigationPane.NavigationMode.SavedSearch:
var item = (ToolStripDropDownButton)TestUtils.GetToolStripItem(MainWindowWrapper.Item, "navigationPane.buttonSearchesBig");
item.ShowDropDown();
WaitFor(() => item.DropDownItems.Count > 0);
item.DropDownItems[0].PerformClick();
break;
case NavigationPane.NavigationMode.Notifications:
break;
}
if (originalMode == mode)
MainWindowWrapper.Item.RequestRefreshTreeView();
});
MWWaitFor(() => GetNavigationMode() == mode);
}
private NavigationPane.NavigationMode GetNavigationMode()
{
switch (MainWindowWrapper.TreeView.Nodes[0].Text)
{
case "Objects by Type":
return NavigationPane.NavigationMode.Objects;
case "Tags":
return NavigationPane.NavigationMode.Tags;
case "Folders":
return NavigationPane.NavigationMode.Folders;
case "Custom Fields":
return NavigationPane.NavigationMode.CustomFields;
case "vApps":
return NavigationPane.NavigationMode.vApps;
case Branding.BRAND_CONSOLE:
return NavigationPane.NavigationMode.Infrastructure;
default:
return NavigationPane.NavigationMode.SavedSearch;
}
}
protected void ApplyTreeSearch(string text)
{
MW(() =>
{
var textbox = TestUtils.GetSearchTextBox(MainWindowWrapper.Item, "navigationPane.navigationView.searchTextBox");
textbox.Text = text;
});
MW(MainWindowWrapper.Item.RequestRefreshTreeView);
}
protected void GoToTabPage(TabPage tabPage)
{
MW(() => MainWindowWrapper.TheTabControl.SelectedTab = tabPage);
}
internal MainWindowWrapper MainWindowWrapper
{
get
{
return new MainWindowWrapper(Program.MainWindow);
}
}
/// <summary>
/// Handles a modal dialog. Opens the specified modal dialog using openDialog. A background thread is then used to wait
/// for the window to appear. It is then closed using closeDialog.
/// </summary>
/// <typeparam name="TDialogWrapper">The type of the dialog wrapper.</typeparam>
/// <param name="windowText">The window text used to detect when the window appears.</param>
/// <param name="openDialog">Used to open the dialog.</param>
/// <param name="closeDialog">Used to close the dialog.</param>
public void HandleModalDialog<TDialogWrapper>(string windowText, MethodInvoker openDialog, Action<TDialogWrapper> closeDialog)
{
Util.ThrowIfParameterNull(openDialog, "openDialog");
Util.ThrowIfParameterNull(closeDialog, "closeDialog");
Type wrappedItemType = typeof(TDialogWrapper).GetProperty("Item").PropertyType;
bool closed = false;
Predicate<Win32Window> match = delegate(Win32Window w)
{
Control ctrl = Control.FromHandle(w.Handle);
return ctrl != null && wrappedItemType.IsAssignableFrom(ctrl.GetType());
};
ThreadPool.QueueUserWorkItem(delegate
{
TDialogWrapper dialog = (TDialogWrapper)Activator.CreateInstance(typeof(TDialogWrapper), WaitForWindowToAppear(windowText, match));
MW(() => closeDialog(dialog));
closed = true;
});
MW(openDialog);
MWWaitFor(() => closed, $"Dialog {windowText} was not closed.");
}
/// <summary>
/// Handles a modeless dialog. Opens a dialog of the specified wrapper type with the specified window text using the
/// openDialog delegate. It then closes it with the closeDialog delegate.
/// </summary>
/// <typeparam name="TDialogWrapper">The type of the dialog to be opened.</typeparam>
/// <param name="windowText">The window text.</param>
/// <param name="openDialog">The delegate to open the dialog.</param>
/// <param name="closeDialog">The delegate to close the dialog.</param>
public void HandleModelessDialog<TDialogWrapper>(string windowText, MethodInvoker openDialog, Action<TDialogWrapper> closeDialog)
{
MW(openDialog);
Type wrappedItemType = typeof(TDialogWrapper).GetProperty("Item").PropertyType;
Predicate<Win32Window> match = w =>
{
var ctrl = Control.FromHandle(w.Handle);
return ctrl != null && wrappedItemType.IsAssignableFrom(ctrl.GetType());
};
var dialog = (TDialogWrapper)Activator.CreateInstance(typeof(TDialogWrapper), WaitForWindowToAppear(windowText, match));
MW(() => closeDialog(dialog));
}
public override List<IXenConnection> ConnectionManager
{
get { return ConnectionsManager.XenConnections; }
}
public override List<IXenConnection> ConnectionManagerCopy
{
get { return ConnectionsManager.XenConnectionsCopy; }
}
}
}