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

Mutations

54

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

68

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

73

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

89

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

94

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

112

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

126

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

131

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

135

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

138

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

157

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

158

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

179

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

180

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

187

1.1
Location : getField
Killed by : none
negated conditional → RUN_ERROR

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

191

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

193

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

194

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

Active mutators

Tests examined


Report generated by PIT 1.20.2