com.anrisoftware.globalpom.utils.TestUtils.groovy Maven / Gradle / Ivy
Show all versions of globalpom-groovytestutils Show documentation
/*
* Copyright 2011-2025 Erwin Müller
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.anrisoftware.globalpom.utils
import java.nio.charset.Charset
import org.apache.commons.io.Charsets
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.SerializationUtils
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.builder.ToStringBuilder
import org.apache.commons.lang3.builder.ToStringStyle
import org.joda.time.Duration
import org.joda.time.ReadableDuration
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import name.fraser.neil.plaintext.diff_match_patch
/**
* Various utilities to simplify the groovy tests.
*
* @author Erwin Mueller, [email protected]
* @since 1.5
*/
class TestUtils {
private final static Logger LOG = LoggerFactory.getLogger(TestUtils)
/**
* Set default to-string style.
*/
public static toStringStyle = ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE)
/**
* Default epsilon for the equality of two values.
*/
static epsilon = 10**-16
/**
* The initial delay before the first action, in milliseconds.
*
* @see #sequencedActions(Object)
*/
static long startDelay = 2000
/**
* The last delay after the actions are executed, in milliseconds.
*
* @see #sequencedActions(Object)
*/
static long endDelay = 2000
/**
* The delay before each action is executed, in milliseconds.
*
* @see #sequencedActions(Object)
*/
static long actionDelay = 1000
/**
* The default {@link Charset} for the tests.
*
* @since 1.10
*/
static Charset charset = Charsets.UTF_8
/**
* Flag if the strings should be trimmed before comparison.
* Default is set to {@code false} which
* means no trimming is done. See {@link String#trim()}.
*
* @since 1.11
*/
static boolean trimStrings = false
/**
* Flag if the line ending should be ignored before comparison of
* strings. Default is set to {@code true} which
* means the line ending is ignored.
*
* On different systems the line ending can be different: Windows is using
* {@code \n\r} and Linux is using {@code \n}. If this flag is set to
* {@code true} the line ending will be treated the same.
*
* @since 1.11
*/
static boolean normalizeLineEnding = true
/**
* Reads the resource with the specified name and specified character set,
* relative to the context class.
*
* @param resource
* the URL of the resource.
*
* @return the content of the resource.
*
* @since 1.10
*/
static String resourceToString(URL resource) {
IOUtils.toString resource, charset
}
/**
* Copy the resource to a target file and make the file executable.
* Create any parent directories of the target.
*
* @since 1.10
*/
static void copyResourceToCommand(URL resource, File target) {
FileUtils.copyURLToFile resource, target
target.setExecutable true, false
}
/**
* Create a temporary file with an optional content.
*
* @param text
* the text context of the file. Default is empty string.
*
* @return the temporary {@link File}.
*
* @since 1.10
*/
static File createTempFile(String text="") {
def tmpFile = File.createTempFile(this.getClass().name, null)
FileUtils.write tmpFile, text, charset
tmpFile.deleteOnExit()
return tmpFile
}
/**
* Assert that the file content equals the given string. If not we create
* a difference patch of the string and the file content.
*
* @param file
* the {@link File} that content is read.
*
* @param expected
* the expected resource {@link URL}, {@link File} or object.
* The resource URL or file is read. If it's neither a
* resource URL nor a file then the object is interpreted
* as a string.
*
* @since 1.11
*/
static void assertFileContent(File file, def expected) {
String fileString = fileToString(file)
String string
if (expected instanceof URL) {
string = resourceToString((URL) expected)
} else if (expected instanceof File) {
string = fileToString((File) expected)
} else {
string = expected.toString()
}
assertStringContent(fileString, string)
}
/**
* Assert that one string equals a different string. If not we create
* a difference patch of the string and the file content. The file content is trimmed before
* comparison, according to {@link #trimStrings}.
*
* @param string
* the test string.
*
* @param expected
* the expected string.
*
* @since 1.11
*/
static void assertStringContent(String string, String expected) {
if (trimStrings) {
expected = expected.trim()
string = string.trim()
}
if (normalizeLineEnding) {
string = StringUtils.replace(string, "\r\n", "\n")
string = StringUtils.replace(string, "\r", "\n")
expected = StringUtils.replace(expected, "\r\n", "\n")
expected = StringUtils.replace(expected, "\r", "\n")
}
if (string != expected) {
def log = LoggerFactory.getLogger(TestUtils)
def diffMatch = new diff_match_patch()
def patch = diffMatch.patch_make string, expected
def diff = diffMatch.patch_toText patch
log.error "String A: \n>>>\n{}<<>>\n{}<<
assert (it - b[idx]).abs() < epsilon : "The difference between $a and $b is greater than $epsilon"
}
}
/**
* Starts the executions of actions after an initial delay. The actions are
* delayed for a fixed amount of time. At the end we have again a delay.
*
* The method is good for actions that the user have to see to verify, like
* GUI actions.
*
* Example with only the initial and end delay:
*
* sequencedActions { }
*
* Example with only one action:
*
* sequencedActions { model.addElement "Ddd" }
*
* Example to execute different GUI related actions:
*
* sequencedActions(
* { childrenPanel.name = name },
* { model.addElement "Ddd" },
* { model.addElement "Eee" },
* { model.addElement "Fff" }
* )
*
*
*
* @param actions
* a list of actions. The first actions is the initial action and is
* executed after the startDelay
delay. Subsequent actions
* are executed after the fixed delay specified in
* actionDelay
. After the last action the delay
* endDelay
is waited.
*
* @param arg
* the argument that is passed to each action.
*
* @see #startDelay
* @see #endDelay
* @see #actionDelay
*
* @since 1.10
*/
static void sequencedActions(Object... actions) {
sequencedActionsWith(null, actions)
}
/**
* Starts the executions of actions after an initial delay. The actions are
* delayed for a fixed amount of time. At the end we have again a delay.
*
* The method is good for actions that the user have to see to verify, like
* GUI actions.
*
* Example with only the initial and end delay:
*
* sequencedActionsWith foo, { }
*
* Example with only one action:
*
* sequencedActions foo, { model.addElement "Ddd" }
*
* Example to execute different GUI related actions:
*
* sequencedActions(foo,
* { childrenPanel.name = name },
* { model.addElement "Ddd" },
* { model.addElement "Eee" },
* { model.addElement "Fff" }
* )
*
*
*
* @param actions
* a list of actions. The first actions is the initial action and is
* executed after the startDelay
delay. Subsequent actions
* are executed after the fixed delay specified in
* actionDelay
. After the last action the delay
* endDelay
is waited.
*
* @param arg
* the argument that is passed to each action.
*
* @see #startDelay
* @see #endDelay
* @see #actionDelay
*
* @since 1.13
*/
static void sequencedActionsWith(def arg, Object... actions) {
Thread.sleep startDelay
actions.first()(arg)
actions.drop(1).each {
Thread.sleep actionDelay
it(arg)
}
Thread.sleep endDelay
}
/**
* Waits for a condition to be true and asserts that the time it took is
* less then the specified timeout duration.
*
*
* waitFor { condition == true }
*
*
* @param condition
* the condition for that we wait for.
*
* @param timeout
* the {@link ReadableDuration} duration for the timeout.
* Defaults to 25 seconds.
*
* @since 1.13
*/
static void waitFor(def condition, ReadableDuration timeout = Duration.parse("PT25S")) {
long time = System.currentTimeMillis()
long timeNow = time
while (!condition() && timeNow - time < timeout.millis) {
Thread.sleep 100
timeNow = System.currentTimeMillis()
}
assert timeNow - time <= timeout.millis
}
}