TestUtil.java

1
package com.reallifedeveloper.tools.test;
2
3
import java.io.BufferedWriter;
4
import java.io.IOException;
5
import java.io.InputStream;
6
import java.lang.reflect.Field;
7
import java.net.ServerSocket;
8
import java.nio.charset.Charset;
9
import java.nio.charset.StandardCharsets;
10
import java.nio.file.Files;
11
import java.nio.file.Paths;
12
import java.text.DateFormat;
13
import java.text.ParseException;
14
import java.text.SimpleDateFormat;
15
import java.time.ZoneOffset;
16
import java.time.ZonedDateTime;
17
import java.util.Arrays;
18
import java.util.Date;
19
import java.util.List;
20
import java.util.Scanner;
21
22
import org.checkerframework.checker.nullness.qual.Nullable;
23
24
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
25
26
/**
27
 * Miscellaneous utility methods that are useful when testing.
28
 *
29
 * @author RealLifeDeveloper
30
 */
31
public final class TestUtil {
32
33
    /**
34
     * The date format used by {@link #parseDate(String)} ({@value #DATE_FORMAT}).
35
     */
36
    public static final String DATE_FORMAT = "yyyy-MM-dd";
37
38
    /**
39
     * The date+time format used by {@link #parseDateTime(String)} ({@value #DATE_TIME_FORMAT}).
40
     */
41
    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
42
43
    /**
44
     * This is a utility class with only static methods, so we hide the only constructor.
45
     */
46
    private TestUtil() {
47
    }
48
49
    /**
50
     * Gives a port number on the local machine that no server process is listening to.
51
     *
52
     * @return a free port number
53
     *
54
     * @throws IOException if an I/O error occurs when trying to open a socket
55
     */
56
    @SuppressFBWarnings(value = "UNENCRYPTED_SERVER_SOCKET", justification = "Server socket only created temporarily to find free port")
57
    public static int findFreePort() throws IOException {
58
        try (ServerSocket server = new ServerSocket(0)) {
59 1 1. findFreePort : replaced int return with 0 for com/reallifedeveloper/tools/test/TestUtil::findFreePort → KILLED
            return server.getLocalPort();
60
        }
61
    }
62
63
    /**
64
     * Parses a date string on the form {@value #DATE_FORMAT} and returns the corresponding {@code java.util.Date} object.
65
     *
66
     * @param date the date string to parse, should be on the form {@value #DATE_FORMAT}
67
     *
68
     * @return the {@code java.util.Date} corresponding to {@code date}
69
     *
70
     * @throws IllegalArgumentException if {@code date} cannot be parsed
71
     */
72
    public static Date parseDate(String date) {
73 1 1. parseDate : negated conditional → KILLED
        if (date == null) {
74
            throw new IllegalArgumentException("date must not be null");
75
        }
76
        DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
77
        try {
78 1 1. parseDate : replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::parseDate → KILLED
            return dateFormat.parse(date);
79
        } catch (ParseException e) {
80
            throw new IllegalArgumentException("Unparseable date: " + date, e);
81
        }
82
    }
83
84
    /**
85
     * Parses a date and time string on the form {@value #DATE_TIME_FORMAT} and returns the corresonding {@code java.util.Date} object.
86
     *
87
     * @param dateTime the date+time string to parse, should be on the form {@value #DATE_TIME_FORMAT}
88
     *
89
     * @return the {@code java.util.Date} corresponding to {@code dateTime}
90
     *
91
     * @throws IllegalArgumentException if {@code dateTime} cannot be parsed
92
     */
93
    public static Date parseDateTime(String dateTime) {
94 1 1. parseDateTime : negated conditional → KILLED
        if (dateTime == null) {
95
            throw new IllegalArgumentException("dateTime must not be null");
96
        }
97
        DateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT);
98
        try {
99 1 1. parseDateTime : replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::parseDateTime → KILLED
            return dateFormat.parse(dateTime);
100
        } catch (ParseException e) {
101
            throw new IllegalArgumentException("Unparseable date/time: " + dateTime, e);
102
        }
103
    }
104
105
    /**
106
     * Gives the current date and time in the UTC time zone.
107
     *
108
     * @return the current UTC date and time
109
     */
110
    public static ZonedDateTime utcNow() {
111 1 1. utcNow : replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::utcNow → KILLED
        return ZonedDateTime.now(ZoneOffset.UTC);
112
    }
113
114
    /**
115
     * Writes a string to a file using the given character encoding.
116
     *
117
     * @param s        the string to write
118
     * @param filename the name of the file to write to
119
     * @param charset  the character set to use, e.g., {@code java.nio.charset.StandardCharsets.UTF_8}
120
     *
121
     * @throws IOException if writing to the file failed
122
     */
123
    @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Use at your own risk")
124
    public static void writeToFile(String s, String filename, Charset charset) throws IOException {
125
        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename), charset)) {
126 1 1. writeToFile : removed call to java/io/BufferedWriter::write → KILLED
            writer.write(s);
127
        }
128
    }
129
130
    /**
131
     * Reads a string from a classpath resource, which is assumed to be UTF-8 encoded text.
132
     *
133
     * @param resourceName the name of the classpath resource to read
134
     *
135
     * @return a string representation of the classpath resource {@code resourceName}
136
     *
137
     * @throws IOException if reading the resource failed
138
     */
139
    public static String readResource(String resourceName) throws IOException {
140 1 1. readResource : negated conditional → KILLED
        if (resourceName == null) {
141
            throw new IllegalArgumentException("resourceName must not be null");
142
        }
143
        StringBuilder sb = new StringBuilder();
144
        try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
145 1 1. readResource : negated conditional → KILLED
            if (is == null) {
146
                throw new IllegalArgumentException("Resource not found: " + resourceName);
147
            }
148
            try (Scanner s = new Scanner(is, StandardCharsets.UTF_8)) {
149 1 1. readResource : negated conditional → KILLED
                while (s.hasNextLine()) {
150
                    sb.append(s.nextLine()).append(System.lineSeparator());
151
                }
152 1 1. readResource : replaced return value with "" for com/reallifedeveloper/tools/test/TestUtil::readResource → KILLED
                return sb.toString();
153
            }
154
        }
155
    }
156
157
    /**
158
     * Injects a value into an object's field, which may be private.
159
     *
160
     * @param obj       the object in which to inject the value
161
     * @param fieldName the name of the field
162
     * @param value     the value to inject, may be {@code null}
163
     *
164
     * @throws IllegalArgumentException if {@code obj} or {@code fieldName} is {@code null}
165
     * @throws IllegalStateException    if reflecction failure
166
     */
167
    @SuppressWarnings("PMD.AvoidAccessibilityAlteration")
168
    public static void injectField(Object obj, String fieldName, @Nullable Object value) {
169
        try {
170
            Field field = getField(obj, fieldName);
171 1 1. injectField : removed call to java/lang/reflect/Field::setAccessible → KILLED
            field.setAccessible(true);
172 1 1. injectField : removed call to java/lang/reflect/Field::set → KILLED
            field.set(obj, value);
173
        } catch (ReflectiveOperationException e) {
174
            throw new IllegalStateException("Error injecting " + value + " into field " + fieldName + " of object " + obj, e);
175
        }
176
    }
177
178
    /**
179
     * Gives the value of an object's field, which may be private.
180
     *
181
     * @param obj       the object containing the field
182
     * @param fieldName the name of the field
183
     *
184
     * @return the value of the field {@code fieldName} in the object {@code obj}
185
     *
186
     * @throws IllegalArgumentException if {@code obj} or {@code fieldName} is {@code null}
187
     * @throws IllegalStateException    if reflection failure
188
     */
189
    @SuppressWarnings("PMD.AvoidAccessibilityAlteration")
190
    public static @Nullable Object getFieldValue(Object obj, String fieldName) {
191
        try {
192
            Field field = getField(obj, fieldName);
193 1 1. getFieldValue : removed call to java/lang/reflect/Field::setAccessible → KILLED
            field.setAccessible(true);
194 1 1. getFieldValue : replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::getFieldValue → KILLED
            return field.get(obj);
195
        } catch (ReflectiveOperationException e) {
196
            throw new IllegalStateException("Error getting value of field " + fieldName + " of object " + obj, e);
197
        }
198
    }
199
200
    private static Field getField(Object obj, String fieldName) throws NoSuchFieldException {
201 2 1. getField : negated conditional → KILLED
2. getField : negated conditional → KILLED
        if (obj == null || fieldName == null) {
202
            throw new IllegalArgumentException("Arguments must not be null: obj=%s, fieldName=%s".formatted(obj, fieldName));
203
        }
204
        Class<?> entityType = obj.getClass();
205 1 1. getField : negated conditional → KILLED
        while (entityType != null) {
206
            for (Field field : entityType.getDeclaredFields()) {
207 1 1. getField : negated conditional → KILLED
                if (field.getName().equals(fieldName)) {
208 1 1. getField : replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::getField → KILLED
                    return field;
209
                }
210
            }
211
            entityType = entityType.getSuperclass();
212
        }
213
        throw new NoSuchFieldException(fieldName);
214
    }
215
216
    /**
217
     * Checks that a value that is declared as {@code @Nullable} actually is non-null.
218
     *
219
     * @param <T> the type of {@code x}
220
     *
221
     * @param x   the value to check
222
     *
223
     * @return {@code x} if it is non-null
224
     *
225
     * @throws IllegalStateException if {@code x} is {@code null}
226
     *
227
     * @see <a href="https://github.com/uber/NullAway/wiki/Suppressing-Warnings">The NullAway documentation</a>
228
     */
229
    public static <T> T castToNonNull(@Nullable T x) {
230 1 1. castToNonNull : negated conditional → KILLED
        if (x == null) {
231
            throw new IllegalStateException("Expected value to be non-null");
232
        }
233 1 1. castToNonNull : replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::castToNonNull → KILLED
        return x;
234
    }
235
236
    /**
237
     * A null-safe version of {@code java.util.Arrays.asList} that converts an array to a list, or returns null if the array is null.
238
     *
239
     * @param <T> the type of the array
240
     * @param a   the array
241
     *
242
     * @return the array as a list, or {@code null} if {@code a} is {@code null}
243
     */
244
    @SuppressWarnings({ "checkstyle:noReturnNull", "PMD.UseVarargs", "PMD.ReturnEmptyCollectionRatherThanNull" })
245
    public static <T> @Nullable List<T> asList(@Nullable T[] a) {
246 1 1. asList : negated conditional → NO_COVERAGE
        if (a == null) {
247 1 1. asList : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/TestUtil::asList → NO_COVERAGE
            return null;
248
        } else {
249 1 1. asList : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/TestUtil::asList → NO_COVERAGE
            return Arrays.asList(a);
250
        }
251
    }
252
}

Mutations

59

1.1
Location : findFreePort
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:findFreePort()]
replaced int return with 0 for com/reallifedeveloper/tools/test/TestUtil::findFreePort → KILLED

73

1.1
Location : parseDate
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:parseNullDate()]
negated conditional → KILLED

78

1.1
Location : parseDate
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:parseDate()]
replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::parseDate → KILLED

94

1.1
Location : parseDateTime
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:parseMalformedDateTime()]
negated conditional → KILLED

99

1.1
Location : parseDateTime
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:parseDateTime()]
replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::parseDateTime → KILLED

111

1.1
Location : utcNow
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:utcNowShouldReturnCurrentDateAndTimeInUtc()]
replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::utcNow → KILLED

126

1.1
Location : writeToFile
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:writeToFile()]
removed call to java/io/BufferedWriter::write → KILLED

140

1.1
Location : readResource
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:readResourceNullResourceName()]
negated conditional → KILLED

145

1.1
Location : readResource
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:readNonExistingResource()]
negated conditional → KILLED

149

1.1
Location : readResource
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:readResource()]
negated conditional → KILLED

152

1.1
Location : readResource
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:readResource()]
replaced return value with "" for com/reallifedeveloper/tools/test/TestUtil::readResource → KILLED

171

1.1
Location : injectField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:injectField()]
removed call to java/lang/reflect/Field::setAccessible → KILLED

172

1.1
Location : injectField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:injectField()]
removed call to java/lang/reflect/Field::set → KILLED

193

1.1
Location : getFieldValue
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:getFieldValue()]
removed call to java/lang/reflect/Field::setAccessible → KILLED

194

1.1
Location : getFieldValue
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:getFieldValue()]
replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::getFieldValue → KILLED

201

1.1
Location : getField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:injectFieldWithNullFieldName()]
negated conditional → KILLED

2.2
Location : getField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:getValueOfNullFieldName()]
negated conditional → KILLED

205

1.1
Location : getField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:injectField()]
negated conditional → KILLED

207

1.1
Location : getField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:injectField()]
negated conditional → KILLED

208

1.1
Location : getField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:injectField()]
replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::getField → KILLED

230

1.1
Location : castToNonNull
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithoutIdAttributeAndNoPrimaryKeyGenerator()]
negated conditional → KILLED

233

1.1
Location : castToNonNull
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithoutIdAttributeAndNoPrimaryKeyGenerator()]
replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::castToNonNull → KILLED

246

1.1
Location : asList
Killed by : none
negated conditional → NO_COVERAGE

247

1.1
Location : asList
Killed by : none
replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/TestUtil::asList → NO_COVERAGE

249

1.1
Location : asList
Killed by : none
replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/TestUtil::asList → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.23.0