// 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; } } }