// Copyright (C) Stichting Deltares 2017. 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;
using System.Collections.Generic;
using System.ComponentModel;
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;
using NUnit.Framework.Internal;
namespace Core.Common.TestUtil
{
///
/// Class containing helper functions which can be used for unit tests.
///
public static class TestHelper
{
private static string solutionRoot;
public static string SolutionRoot
{
get
{
return solutionRoot ?? (solutionRoot = GetSolutionRoot());
}
}
///
/// Returns the location on disk that can be used safely for writing to disk temporarily.
///
/// The folder path.
/// Caller is responsible for cleaning up files put in the folder.
/// Thrown when the folder doesn't exist.
///
public static string GetScratchPadPath()
{
string scratchPadPath = Path.Combine(Path.GetDirectoryName(SolutionRoot), "Scratchpad");
if (!Directory.Exists(scratchPadPath))
{
throw new IOException("The 'Scratchpad' folder has been deleted from the trunk, while tests require the existence of this folder for writing to disk temporarily.");
}
return scratchPadPath;
}
///
/// Returns the location on disk that can be used safely for writing to disk temporarily.
///
/// The file or folder path inside the 'scratchpad' folder.
/// The folder path.
/// Caller is responsible for cleaning up files put in the folder.
/// Thrown when the folder doesn't exist.
///
public static string GetScratchPadPath(string path)
{
return Path.Combine(GetScratchPadPath(), path);
}
///
/// Returns a full path to a test data directory given the .
///
/// The path to construct a full test data path for.
/// A full path to the test data.
public static string GetTestDataPath(TestDataPath testDataPath)
{
return Path.Combine(Path.GetDirectoryName(SolutionRoot), testDataPath.Path, "test-data");
}
///
/// Returns a full path to a file or directory in the test data directory.
///
/// The path to construct a full test data path for.
/// The path to a file or directory in the test data directory.
/// A full path to the file or directory from the test data directory.
public static string GetTestDataPath(TestDataPath testDataPath, string path)
{
return Path.Combine(GetTestDataPath(testDataPath.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)
{
try
{
using (File.OpenWrite(pathToFile)) {}
return true;
}
catch (IOException)
{
return false;
}
}
///
/// Checks whether the directory pointed at by can be used
/// for writing a file.
///
/// The location of the directory to open for writing.
/// true if the file could be opened with write permissions. false otherwise.
/// Thrown when is null.
/// Thrown when contains invalid characters.
public static bool CanWriteInDirectory(string pathToDirectory)
{
string filePath = Path.Combine(pathToDirectory, nameof(CanWriteInDirectory));
try
{
using (File.OpenWrite(filePath)) {}
}
catch (SystemException)
{
return false;
}
if (File.Exists(filePath))
{
File.Delete(filePath);
}
return true;
}
///
/// Converts a rooted file or folder path into a UNC-path.
///
/// The rooted path.
/// The UNC-path.
/// Thrown when
/// contains invalid characters, is null or empty or wasn't a rooted path.
public static string ToUncPath(string rootedPath)
{
string root = Path.GetPathRoot(rootedPath);
if (string.IsNullOrEmpty(root))
{
throw new ArgumentException("Must be a rooted path.", nameof(rootedPath));
}
string relativePath = rootedPath.Replace(root, "");
string drive = root.Remove(1);
var uncPath = new Uri(Path.Combine(@"\\localhost", drive + "$", relativePath));
return uncPath.LocalPath;
}
///
/// Asserts that the execution of some implementation of a functionality runs faster than a given allowed time.
///
/// The maximum time in milliseconds that the functionality is allowed to run.
/// The functionality to execute.
/// Take HDD speed into account, makes sure that test timing is divided by MACHINE_HDD_PERFORMANCE_RANK environmental variable.
public static void AssertIsFasterThan(float maxMilliseconds, Action action, bool rankHddAccess = false)
{
AssertIsFasterThan(maxMilliseconds, null, action, rankHddAccess);
}
///
/// 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 occurred 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)
{
Tuple[] renderedMessages = GetAllRenderedMessages(action).ToArray();
AssertExpectedMessagesInRenderedMessages(messages, renderedMessages);
if (expectedLogMessageCount != null)
{
Assert.AreEqual((int) expectedLogMessageCount, renderedMessages.Length);
}
}
///
/// 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)
{
IEnumerable> renderedMessages = GetAllRenderedMessages(action);
assertLogMessages(renderedMessages.Select(rm => rm.Item1));
}
///
/// Method used 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 and logged exceptions.
public static void AssertLogMessagesWithLevelAndLoggedExceptions(
Action action,
Action>> assertLogMessagesWithLevelAndExceptions)
{
IEnumerable> renderedMessages = GetAllRenderedMessagesWithExceptions(action);
assertLogMessagesWithLevelAndExceptions(renderedMessages);
}
///
/// 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)
{
IEnumerable> renderedMessages = GetAllRenderedMessages(action);
Assert.AreEqual(count, renderedMessages.Count());
}
///
/// Asserts that two bitmap images are equal.
///
/// The expected image.
/// The actual image.
/// Thrown 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.
/// Thrown 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.
/// Thrown 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);
string message = exception.Message;
if (exception.ParamName != null)
{
List 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 occurred 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 occurred 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)
{
Tuple[] renderedMessages = GetAllRenderedMessages(action).ToArray();
AssertExpectedMessagesInRenderedMessages(messages, renderedMessages);
if (expectedLogMessageCount != null)
{
Assert.AreEqual((int) expectedLogMessageCount, renderedMessages.Length);
}
}
///
/// Determines whether a property is decorated with a
/// of a given type.
///
/// The type of the target to retrieve the property from.
/// The type of to check
/// for on the property of .
/// The expression that resolves to the property to be checked.
/// Thrown when the
/// wasn't decorated with a or with a different
/// than .
/// Thrown when is null.
/// Thrown when
/// is not an expression with a property, such as an expression calling multiple methods.
/// Thrown when more than one property is found with
/// name specified in .
/// Thrown when a custom attribute type cannot be loaded.
/// Thrown when the property in
/// belongs to a type that is loaded into the reflection-only context. See How to:
/// Load Assemblies into the Reflection-Only Context on MSDN for more information.
public static void AssertTypeConverter(string expression) where TTypeConverter : TypeConverter
{
var typeConverterAttribute = (TypeConverterAttribute) Attribute.GetCustomAttribute(typeof(TTarget).GetProperty(expression),
typeof(TypeConverterAttribute),
true);
Assert.NotNull(typeConverterAttribute);
Assert.IsTrue(typeConverterAttribute.ConverterTypeName == typeof(TTypeConverter).AssemblyQualifiedName);
}
///
/// Determines whether objects are copies of each other by verifying that they are
/// equal, but not the same.
///
/// The object which should be equal, but not same as .
/// The object which should be equal, but not same as .
/// Thrown when either:
///
/// - is not equal to ;
/// - is the same as .
///
///
public static void AssertAreEqualButNotSame(object objectA, object objectB)
{
Assert.AreEqual(objectA, objectB, "Objects should be equal.");
if (objectA != null)
{
Assert.AreNotSame(objectA, objectB, "Objects should not be the same.");
}
}
///
/// Asserts that all elements in the collections are the same.
///
/// The expected collection of elements.
/// The actual collection of elements.
/// Thrown when either:
///
/// - has more or less elements than ;
/// - contains an element at a position that is not the same
/// element in at that position.
///
///
public static void AssertCollectionAreSame(IEnumerable expected, IEnumerable actual)
{
IEnumerator expectedEnumerator = expected.GetEnumerator();
IEnumerator actualEnumerator = actual.GetEnumerator();
while (expectedEnumerator.MoveNext())
{
Assert.IsTrue(actualEnumerator.MoveNext());
{
Assert.AreSame(expectedEnumerator.Current, actualEnumerator.Current);
}
}
Assert.IsFalse(actualEnumerator.MoveNext());
}
///
/// Asserts that all elements in the collections are not the same.
///
/// The expected collection of elements.
/// The actual collection of elements.
/// Thrown when either:
///
/// - has more or less elements than ;
/// - contains an element at a position that is the same
/// element in at that position.
///
///
public static void AssertCollectionAreNotSame(IEnumerable expected, IEnumerable actual)
{
IEnumerator expectedEnumerator = expected.GetEnumerator();
IEnumerator actualEnumerator = actual.GetEnumerator();
while (expectedEnumerator.MoveNext())
{
Assert.IsTrue(actualEnumerator.MoveNext());
{
Assert.AreNotSame(expectedEnumerator.Current, actualEnumerator.Current);
}
}
Assert.IsFalse(actualEnumerator.MoveNext());
}
///
/// Asserts that all elements in the collections are equal by using the .
///
/// The expected collection of elements.
/// The actual collection of elements.
/// The comparer to verify whether elements of
/// and are equal.
/// Thrown when either:
///
/// - has more or less elements than ;
/// - contains an element at a position that is not equal to the
/// element in at that position.
///
///
public static void AssertCollectionsAreEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer equalityComparer)
{
Assert.NotNull(expected, "Expect collection is not expected to be null.");
Assert.NotNull(actual, "Actual collection is not expected to be null.");
Assert.NotNull(equalityComparer, "Comparer to use for equality cannot be null.");
using (IEnumerator expectedEnumerator = expected.GetEnumerator())
using (IEnumerator actualEnumerator = actual.GetEnumerator())
{
while (expectedEnumerator.MoveNext())
{
Assert.IsTrue(actualEnumerator.MoveNext());
{
Assert.IsTrue(equalityComparer.Equals(expectedEnumerator.Current, actualEnumerator.Current));
}
}
Assert.IsFalse(actualEnumerator.MoveNext());
}
}
private static void AssertIsFasterThan(float maxMilliseconds, string message, Action action, bool rankHddAccess)
{
var stopwatch = new Stopwatch();
double 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();
float machineHddPerformanceRank = GetMachineHddPerformanceRank();
float rank = machineHddPerformanceRank;
if (rankHddAccess) // when test relies a lot on HDD - multiply rank by hdd speed factor
{
rank *= machineHddPerformanceRank;
}
string 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));
}
}
private static void AssertExpectedMessagesInRenderedMessages(IEnumerable messages, Tuple[] 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, Tuple[] renderedMessages)
{
IEnumerable> 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);
ToolStripItem item = items[position];
Assert.AreEqual(text, item.Text);
Assert.AreEqual(toolTip, item.ToolTipText);
Assert.AreEqual(enabled, item.Enabled);
AssertImagesAreEqual(icon, item.Image);
}
private static string GetSolutionRoot()
{
const string solutionName = "Ringtoets.sln";
//get the current directory and scope up
//TODO find a faster safer method
var testContext = new TestContext(TestExecutionContext.CurrentContext);
string curDir = testContext.TestDirectory;
while (Directory.Exists(curDir) && !File.Exists(curDir + @"\" + solutionName))
{
curDir += "/../";
}
if (!File.Exists(Path.Combine(curDir, solutionName)))
{
throw new InvalidOperationException(string.Format("Solution file '{0}' not found in any folder of '{1}'.",
solutionName,
Directory.GetCurrentDirectory()));
}
return Path.GetFullPath(curDir);
}
private static float GetMachineHddPerformanceRank()
{
string rank = Environment.GetEnvironmentVariable("MACHINE_HDD_PERFORMANCE_RANK");
if (!string.IsNullOrEmpty(rank))
{
return float.Parse(rank);
}
return 1.0f;
}
private static IEnumerable> GetAllRenderedMessages(Action action)
{
IEnumerable> renderedMessages = GetAllRenderedMessagesWithExceptions(action);
return renderedMessages.Select(t => Tuple.Create(t.Item1, t.Item2));
}
private static IEnumerable> GetAllRenderedMessagesWithExceptions(Action action)
{
var memoryAppender = new MemoryAppender();
BasicConfigurator.Configure(memoryAppender);
LogHelper.SetLoggingLevel(Level.All);
action();
List> renderedMessages = memoryAppender.GetEvents()
.Select(le => Tuple.Create(le.RenderedMessage,
le.Level,
le.ExceptionObject))
.ToList();
memoryAppender.Close();
LogHelper.ResetLogging();
return renderedMessages;
}
private static Color[] GetImageAsColorArray(Image image)
{
// Convert image to ARGB bitmap using 8bits/channel:
Bitmap 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 (var i = 0; i < bitmap.Height; i++)
{
for (var j = 0; j < bitmap.Width; j++)
{
imageColors[index++] = bitmap.GetPixel(j, i);
}
}
return imageColors;
}
}
}