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

Mutations

56

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

70

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

75

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

91

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

96

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

108

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

123

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

137

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

142

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

146

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

149

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

168

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

169

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

190

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

191

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

198

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

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

202

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

204

1.1
Location : getField
Killed by : com.reallifedeveloper.tools.test.TestUtilTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.TestUtilTest]/[method:injectFieldNullValue()]
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:injectFieldNullValue()]
replaced return value with null for com/reallifedeveloper/tools/test/TestUtil::getField → KILLED

Active mutators

Tests examined


Report generated by PIT 1.20.2