// Copyright (C) Stichting Deltares 2016. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using NUnit.Framework;
namespace Core.Common.TestUtil
{
public static class TestHelper
{
private static string solutionRoot;
public static string SolutionRoot
{
get
{
if (solutionRoot == null)
{
solutionRoot = GetSolutionRoot();
}
return solutionRoot;
}
}
//TODO: Replace this property
public static string TestDataDirectory
{
get
{
return Path.GetDirectoryName(SolutionRoot);
}
}
public static string GetCurrentMethodName()
{
MethodBase callingMethod = new StackFrame(1, false).GetMethod();
return callingMethod.DeclaringType.Name + "." + callingMethod.Name;
}
///
/// Returns full path to the file or directory in "test-data"
///
///
///
public static string GetTestDataPath(TestDataPath testDataPath)
{
return Path.Combine(TestDataDirectory, testDataPath.Path, "test-data");
}
///
/// Returns full path to the file or directory in "test-data"
///
///
///
///
public static string GetTestDataPath(TestDataPath testDataPath, string path)
{
return Path.Combine(GetTestDataPath(testDataPath.Path), path);
}
public static string GetTestProjectDirectory()
{
var stackFrames = new StackTrace().GetFrames();
if (stackFrames == null)
{
throw new Exception("Could not get stacktrace.");
}
var testMethod = stackFrames.FirstOrDefault(f => f.GetMethod().GetCustomAttributes(typeof(TestAttribute), true).Any() ||
f.GetMethod().GetCustomAttributes(typeof(SetUpAttribute), true).Any() ||
f.GetMethod().GetCustomAttributes(typeof(TestFixtureSetUpAttribute), true).Any());
if (testMethod == null)
{
throw new Exception("Could not determine the test method.");
}
var testClassType = testMethod.GetMethod().DeclaringType;
if (testClassType == null)
{
throw new Exception("Could not find test class type.");
}
return Path.GetDirectoryName((new Uri(testClassType.Assembly.CodeBase)).AbsolutePath);
}
///
/// Gets the test-data directory for the current test project.
///
public static string GetDataDir()
{
var testProjectDirectory = GetTestProjectDirectory();
var rootedTestProjectFolderPath = Path.GetFullPath(Path.Combine(testProjectDirectory, "..", ".."));
return Path.GetFullPath(Path.Combine(rootedTestProjectFolderPath, "test-data") + Path.DirectorySeparatorChar);
}
///
/// Get's the path in test-data tree section
///
///
///
public static string GetTestFilePath(string filename)
{
var path = Path.Combine(GetDataDir(), filename);
var uri = new UriBuilder(path);
path = Uri.UnescapeDataString(uri.Path);
if (File.Exists(path))
{
return path;
}
// file not found..exception
throw new FileNotFoundException(String.Format("File not found: {0}", path), path);
}
///
/// Checks whether the file pointed at by can be opened
/// for writing.
///
/// The location of the file to open for writing.
/// true if the file could be opened with write permissions. false otherwise.
public static bool CanOpenFileForWrite(string pathToFile)
{
FileStream file = null;
try
{
file = File.OpenWrite(pathToFile);
return true;
}
catch (IOException)
{
return false;
}
finally
{
if (file != null)
{
file.Close();
}
}
}
///
///
///
///
///
/// Take HDD speed into account, makes sure that test timing is divided by MACHINE_HDD_PERFORMANCE_RANK environmental variable.
///
public static double AssertIsFasterThan(float maxMilliseconds, Action action, bool rankHddAccess = false)
{
return AssertIsFasterThan(maxMilliseconds, null, action, rankHddAccess);
}
///
///
///
///
///
///
/// Take HDD speed into account, makes sure that test timing is divided by MACHINE_HDD_PERFORMANCE_RANK environmental variable.
///
public static double AssertIsFasterThan(float maxMilliseconds, string message, Action action, bool rankHddAccess)
{
var stopwatch = new Stopwatch();
var actualMillisecond = default(double);
stopwatch.Start();
action();
stopwatch.Stop();
actualMillisecond = Math.Abs(actualMillisecond - default(double)) > 1e-5
? Math.Min(stopwatch.ElapsedMilliseconds, actualMillisecond)
: stopwatch.ElapsedMilliseconds;
stopwatch.Reset();
var machineHddPerformanceRank = GetMachineHddPerformanceRank();
var rank = machineHddPerformanceRank;
if (rankHddAccess) // when test relies a lot on HDD - multiply rank by hdd speed factor
{
rank *= machineHddPerformanceRank;
}
var userMessage = string.IsNullOrEmpty(message) ? "" : message + ". ";
if (!rank.Equals(1.0f))
{
Assert.IsTrue(rank*actualMillisecond < maxMilliseconds, userMessage + "Maximum of {0} milliseconds exceeded. Actual was {1}, machine performance weighted actual was {2}",
maxMilliseconds, actualMillisecond, actualMillisecond*rank);
Console.WriteLine(userMessage + string.Format("Test took {1} milliseconds (machine performance weighted {2}). Maximum was {0}",
maxMilliseconds, actualMillisecond, actualMillisecond*rank));
}
else
{
Assert.IsTrue(actualMillisecond < maxMilliseconds, userMessage + "Maximum of {0} milliseconds exceeded. Actual was {1}", maxMilliseconds,
actualMillisecond);
Console.WriteLine(userMessage + string.Format("Test took {1} milliseconds. Maximum was {0}", maxMilliseconds, actualMillisecond));
}
return actualMillisecond;
}
///
/// Checks if the given messages occurs in the log.
///
/// Action to be performed while recording the log
/// The message that should occur in the log
/// Optional: assert that log has this number of messages.
public static void AssertLogMessageIsGenerated(Action action, string message, int? expectedLogMessageCount = null)
{
AssertLogMessagesAreGenerated(action, new[]
{
message
}, expectedLogMessageCount);
}
///
/// Method used to check if a collection of messages have occured in the log.
/// This function allowed checking log messages being generated like
/// without having to rerun the action for every single message. Fails the test when any of checks fail.
///
/// Action to be performed while recording the log
/// The collection of messages that should occur in the log
/// Optional: assert that log has this number of messages.
///
public static void AssertLogMessagesAreGenerated(Action action, IEnumerable messages, int? expectedLogMessageCount = null)
{
var renderedMessages = GetAllRenderedMessages(action);
AssertExpectedMessagesInRenderedMessages(messages, renderedMessages);
if (expectedLogMessageCount != null)
{
Assert.AreEqual((int) expectedLogMessageCount, renderedMessages.Count());
}
}
///
/// Method use to perform any type of assertion on the generated log while performing
/// a particular action.
///
/// Action to be performed while recording the log.
/// The assertion logic performed on the generated log-messages.
public static void AssertLogMessages(Action action, Action> assertLogMessages)
{
var renderedMessages = GetAllRenderedMessages(action);
assertLogMessages(renderedMessages.Select(rm => rm.Item1));
}
///
/// Checks the number of messages in the log.
///
/// Action to be performed while recording the log
/// The expected number of messages
public static void AssertLogMessagesCount(Action action, int count)
{
var renderedMessages = GetAllRenderedMessages(action);
Assert.AreEqual(count, renderedMessages.Count());
}
///
/// Asserts that two bitmap images are equal.
///
/// The expected image.
/// The actual image.
/// When is not
/// equal to .
public static void AssertImagesAreEqual(Image expectedImage, Image actualImage)
{
if (expectedImage == null)
{
Assert.IsNull(actualImage);
return;
}
Assert.IsNotNull(actualImage);
Assert.AreEqual(expectedImage.Size, actualImage.Size);
IEnumerable expectedImageBytes = GetImageAsColorArray(expectedImage);
IEnumerable actualImageBytes = GetImageAsColorArray(actualImage);
CollectionAssert.AreEqual(expectedImageBytes, actualImageBytes);
}
///
/// Asserts that a contains an item at the given
/// with the correct properties.
///
/// The containing an item at position .
/// The position of the menu item in .
/// The text expected for the menu item.
/// The tooltip expected for the menu item.
/// The image expected for the menu item.
/// Optional: the expected enabled state of the menu item. Default: true.
/// When does not contain a menu item at
/// position with the right , or .
///
public static void AssertContextMenuStripContainsItem(ContextMenuStrip menu, int position, string text, string toolTip, Image icon, bool enabled = true)
{
Assert.IsNotNull(menu);
AssertContextMenuStripContainsItem(menu.Items, position, text, toolTip, icon, enabled);
}
///
/// Asserts that a contains an item at the given
/// with the correct properties.
///
/// The containing an item at position .
/// The position of the menu item in .
/// The text expected for the menu item.
/// The tooltip expected for the menu item.
/// The image expected for the menu item.
/// Optional: the expected enabled state of the menu item. Default: true.
/// When does not contain a menu item at
/// position with the right , or .
///
public static void AssertDropDownItemContainsItem(ToolStripDropDownItem menu, int position, string text, string toolTip, Image icon, bool enabled = true)
{
Assert.IsNotNull(menu);
AssertContextMenuStripContainsItem(menu.DropDownItems, position, text, toolTip, icon, enabled);
}
///
/// Asserts that the exception is of type and that the custom part of
/// is equal to .
///
/// The type of the expected .
/// The test to execute and should throw of type .
/// The expected custom part of the .
/// The that was thrown while executing .
public static T AssertThrowsArgumentExceptionAndTestMessage(TestDelegate test, string expectedCustomMessage) where T : ArgumentException
{
var exception = Assert.Throws(test);
var message = exception.Message;
if (exception.ParamName != null)
{
var customMessageParts = message.Split(new[]
{
Environment.NewLine
}, StringSplitOptions.None).ToList();
customMessageParts.RemoveAt(customMessageParts.Count - 1);
message = string.Join(Environment.NewLine, customMessageParts.ToArray());
}
Assert.AreEqual(expectedCustomMessage, message);
return exception;
}
///
/// Method used to check if a collection of messages have occured in the log.
/// This function allowed checking log messages being generated like
/// without having to rerun the action for every single message. Fails the test when any of checks fail.
///
/// Action to be performed while recording the log.
/// The message that should occur in the log with a certain log level.
/// Optional: assert that log has this number of messages.
///
public static void AssertLogMessageWithLevelIsGenerated(Action action, Tuple message, int? expectedLogMessageCount = null)
{
AssertLogMessagesWithLevelAreGenerated(action, new[]
{
message
}, expectedLogMessageCount);
}
///
/// Method used to check if a collection of messages have occured in the log.
/// This function allowed checking log messages being generated like
/// without having to rerun the action for every single message. Fails the test when any of checks fail.
///
/// Action to be performed while recording the log
/// The collection of messages that should occur in the log
/// Optional: assert that log has this number of messages.
///
public static void AssertLogMessagesWithLevelAreGenerated(Action action, IEnumerable> messages, int? expectedLogMessageCount = null)
{
var renderedMessages = GetAllRenderedMessages(action);
AssertExpectedMessagesInRenderedMessages(messages, renderedMessages);
if (expectedLogMessageCount != null)
{
Assert.AreEqual((int) expectedLogMessageCount, renderedMessages.Count());
}
}
private static void AssertExpectedMessagesInRenderedMessages(IEnumerable messages, IEnumerable> renderedMessages)
{
foreach (string message in messages)
{
CollectionAssert.Contains(renderedMessages.Select(rm => rm.Item1), message);
}
}
///
/// Checks if all messages from occur in
///
/// The collection of expected messages
/// The collection of messages in the log
private static void AssertExpectedMessagesInRenderedMessages(IEnumerable> messages, IEnumerable> renderedMessages)
{
var messagesWithLog4NetLevel = messages.Select(m => Tuple.Create(m.Item1, m.Item2.ToLog4NetLevel()));
foreach (Tuple message in messagesWithLog4NetLevel)
{
CollectionAssert.Contains(renderedMessages, message);
}
}
private static void AssertContextMenuStripContainsItem(ToolStripItemCollection items, int position, string text, string toolTip, Image icon, bool enabled = true)
{
Assert.Less(position, items.Count);
var item = items[position];
Assert.AreEqual(text, item.Text);
Assert.AreEqual(toolTip, item.ToolTipText);
Assert.AreEqual(enabled, item.Enabled);
AssertImagesAreEqual(icon, item.Image);
}
///
/// Create dir if not exists.
///
/// File path to a directory.
/// When:
/// The directory specified by is read-only
///
/// When: The caller does not have the required permission.
///
/// is a zero-length string, contains only white space, or contains one or more invalid characters as defined by . -or-
/// is prefixed with, or contains only a colon character (:).
/// is null.
///
/// The specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters and file names must be less than 260 characters.
/// The specified path is invalid (for example, it is on an unmapped drive).
/// contains a colon character (:) that is not part of a drive label ("C:\").
private static void CreateDirectoryIfNotExists(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
private static string GetCurrentTestClassMethodName()
{
var stackTrace = new StackTrace(false);
for (int i = 1; i < stackTrace.FrameCount; i++)
{
StackFrame stackFrame = stackTrace.GetFrame(i);
if (stackFrame.GetMethod().GetCustomAttributes(true).OfType().Count() != 0)
{
MethodBase method = stackFrame.GetMethod();
return method.DeclaringType.Name + "." + method.Name;
}
}
return "";
}
private static string GetSolutionRoot()
{
const string solutionName = "Ringtoets.sln";
//get the current directory and scope up
//TODO find a faster safer method
string curDir = ".";
while (Directory.Exists(curDir) && !File.Exists(curDir + @"\" + solutionName))
{
curDir += "/../";
}
if (!File.Exists(Path.Combine(curDir, solutionName)))
{
throw new InvalidOperationException("Solution file not found.");
}
return Path.GetFullPath(curDir);
}
private static float GetMachineHddPerformanceRank()
{
string rank = Environment.GetEnvironmentVariable("MACHINE_HDD_PERFORMANCE_RANK");
if (!String.IsNullOrEmpty(rank))
{
return Single.Parse(rank);
}
return 1.0f;
}
private static float GetMachinePerformanceRank()
{
string rank = Environment.GetEnvironmentVariable("MACHINE_PERFORMANCE_RANK");
if (!String.IsNullOrEmpty(rank))
{
return Single.Parse(rank);
}
return 1.0f;
}
private static IEnumerable> GetAllRenderedMessages(Action action)
{
var memoryAppender = new MemoryAppender();
BasicConfigurator.Configure(memoryAppender);
LogHelper.SetLoggingLevel(Level.All);
action();
var renderedMessages = memoryAppender.GetEvents().Select(le => Tuple.Create(le.RenderedMessage, le.Level)).ToList();
memoryAppender.Close();
LogHelper.ResetLogging();
return renderedMessages;
}
private static Color[] GetImageAsColorArray(Image image)
{
// Convert image to ARGB bitmap using 8bits/channel:
var bitmap = new Bitmap(image).Clone(new Rectangle(0, 0, image.Size.Width, image.Size.Height), PixelFormat.Format32bppArgb);
var index = 0;
var imageColors = new Color[image.Size.Width*image.Size.Height];
for (int i = 0; i < bitmap.Height; i++)
{
for (int j = 0; j < bitmap.Width; j++)
{
imageColors[index++] = bitmap.GetPixel(i, j);
}
}
return imageColors;
}
}
}