TestUtil.java
package com.reallifedeveloper.tools.test;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Miscellaneous utility methods that are useful when testing.
*
* @author RealLifeDeveloper
*/
public final class TestUtil {
/**
* The date format used by {@link #parseDate(String)} ({@value #DATE_FORMAT}).
*/
public static final String DATE_FORMAT = "yyyy-MM-dd";
/**
* The date+time format used by {@link #parseDateTime(String)} ({@value #DATE_TIME_FORMAT}).
*/
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* This is a utility class with only static methods, so we hide the only constructor.
*/
private TestUtil() {
}
/**
* Gives a port number on the local machine that no server process is listening to.
*
* @return a free port number
*
* @throws IOException if an I/O error occurs when trying to open a socket
*/
public static int findFreePort() throws IOException {
try (ServerSocket server = new ServerSocket(0)) {
return server.getLocalPort();
}
}
/**
* Parses a date string on the form {@value #DATE_FORMAT} and returns the corresponding {@code java.util.Date} object.
*
* @param date the date string to parse, should be on the form {@value #DATE_FORMAT}
*
* @return the {@code java.util.Date} corresponding to {@code date}
*
* @throws IllegalArgumentException if {@code date} cannot be parsed
*/
public static Date parseDate(String date) {
if (date == null) {
throw new IllegalArgumentException("date must not be null");
}
DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
try {
return dateFormat.parse(date);
} catch (ParseException e) {
throw new IllegalArgumentException("Unparseable date: " + date, e);
}
}
/**
* Parses a date and time string on the form {@value #DATE_TIME_FORMAT} and returns the corresonding {@code java.util.Date} object.
*
* @param dateTime the date+time string to parse, should be on the form {@value #DATE_TIME_FORMAT}
*
* @return the {@code java.util.Date} corresponding to {@code dateTime}
*
* @throws IllegalArgumentException if {@code dateTime} cannot be parsed
*/
public static Date parseDateTime(String dateTime) {
if (dateTime == null) {
throw new IllegalArgumentException("dateTime must not be null");
}
DateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT);
try {
return dateFormat.parse(dateTime);
} catch (ParseException e) {
throw new IllegalArgumentException("Unparseable date/time: " + dateTime, e);
}
}
/**
* Writes a string to a file using the given character encoding.
*
* @param s the string to write
* @param filename the name of the file to write to
* @param charset the character set to use, e.g., {@code java.nio.charset.StandardCharsets.UTF_8}
*
* @throws IOException if writing to the file failed
*/
public static void writeToFile(String s, String filename, Charset charset) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename), charset)) {
writer.write(s);
}
}
/**
* Reads a string from a classpath resource, which is assumed to be UTF-8 encoded text.
*
* @param resourceName the name of the classpath resource to read
*
* @return a string representation of the classpath resource {@code resourceName}
*
* @throws IOException if reading the resource failed
*/
public static String readResource(String resourceName) throws IOException {
if (resourceName == null) {
throw new IllegalArgumentException("resourceName must not be null");
}
StringBuilder sb = new StringBuilder();
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
if (is == null) {
throw new IllegalArgumentException("Resource not found: " + resourceName);
}
try (Scanner s = new Scanner(is, "UTF-8")) {
while (s.hasNextLine()) {
sb.append(s.nextLine()).append(System.lineSeparator());
}
return sb.toString();
}
}
}
/**
* Injects a value into an object's field, which may be private.
*
* @param obj the object in which to inject the value
* @param fieldName the name of the field
* @param value the value to inject
*
* @throws IllegalArgumentException if {@code obj} or {@code fieldName} is {@code null}
* @throws IllegalStateException if reflecction failure
*/
@SuppressWarnings("PMD.AvoidAccessibilityAlteration")
public static void injectField(Object obj, String fieldName, Object value) {
try {
Field field = getField(obj, fieldName);
field.setAccessible(true);
field.set(obj, value);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Error injecting " + value + " into field " + fieldName + " of object " + obj, e);
}
}
/**
* Gives the value of an object's field, which may be private.
*
* @param obj the object containing the field
* @param fieldName the name of the field
*
* @return the value of the field {@code fieldName} in the object {@code obj}
*
* @throws IllegalArgumentException if {@code obj} or {@code fieldName} is {@code null}
* @throws IllegalStateException if reflection failure
*/
@SuppressWarnings("PMD.AvoidAccessibilityAlteration")
public static @Nullable Object getFieldValue(Object obj, String fieldName) {
try {
Field field = getField(obj, fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Error getting value of field " + fieldName + " of object " + obj, e);
}
}
private static Field getField(Object obj, String fieldName) throws NoSuchFieldException {
if (obj == null || fieldName == null) {
throw new IllegalArgumentException("Arguments must not be null: obj=%s, fieldName=%s".formatted(obj, fieldName));
}
Class<?> entityType = obj.getClass();
while (entityType != null) {
for (Field field : entityType.getDeclaredFields()) {
if (field.getName().equals(fieldName)) {
return field;
}
}
entityType = entityType.getSuperclass();
}
throw new NoSuchFieldException(fieldName);
}
}