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 |
|
68 |
1.1 |
|
73 |
1.1 |
|
89 |
1.1 |
|
94 |
1.1 |
|
112 |
1.1 |
|
126 |
1.1 |
|
131 |
1.1 |
|
135 |
1.1 |
|
138 |
1.1 |
|
157 |
1.1 |
|
158 |
1.1 |
|
179 |
1.1 |
|
180 |
1.1 |
|
187 |
1.1 2.2 |
|
191 |
1.1 |
|
193 |
1.1 |
|
194 |
1.1 |