TestFramework.java

1
package com.renomad.minum.testing;
2
3
import com.renomad.minum.state.Constants;
4
import com.renomad.minum.state.Context;
5
import com.renomad.minum.logging.TestLogger;
6
import com.renomad.minum.queue.ActionQueueKiller;
7
import com.renomad.minum.utils.ThrowingRunnable;
8
import com.renomad.minum.web.FullSystem;
9
10
import java.util.Arrays;
11
import java.util.List;
12
import java.util.Properties;
13
import java.util.concurrent.Executors;
14
15
/**
16
 * These are utility functions for basic automated
17
 * testing.  It turns out you don't really need fancy tools
18
 * to do excellent testing.  Just a commitment to
19
 * quality.  Don't let anyone tell you differently.
20
 * <br>
21
 * Hint: A common pattern you will use before testing is to initialize the {@link Context} object
22
 * using {@link #buildTestingContext}.
23
 */
24
public final class TestFramework {
25
26
    private TestFramework() {
27
        // making this private to be clearer it isn't supposed to be instantiated.
28
    }
29
30
    /**
31
     * assert that a particular chunk of code throws a particular
32
     * exception.
33
     * <p>
34
     *     Example usage:
35
     * </p>
36
     * <pre>
37
     *     <code>
38
     *         {@code assertThrows(TemplateRenderException.class, "Missing a value for key {missing_key}", () -> tp.renderTemplate(myMap));}
39
     *     </code>
40
     * </pre>
41
     */
42
    public static <T> T assertThrows(Class<T> myEx, ThrowingRunnable r) {
43 1 1. assertThrows : replaced return value with null for com/renomad/minum/testing/TestFramework::assertThrows → KILLED
        return assertThrows(myEx, null, r);
44
    }
45
46
    // quick note about the warning suppression - we already checked that the
47
    // case will be valid, when we checked if (!myEx.isInstance(ex)).
48
    @SuppressWarnings("unchecked")
49
    public static <T> T assertThrows(Class<T> myEx, String expectedMsg, ThrowingRunnable r) {
50
        try {
51 1 1. assertThrows : removed call to com/renomad/minum/utils/ThrowingRunnable::run → KILLED
            r.run();
52
            throw new TestFailureException("Failed to throw exception");
53
        } catch (Exception ex) {
54 1 1. assertThrows : negated conditional → KILLED
            if (!myEx.getTypeName().equals(ex.getClass().getTypeName())) {
55
                String msg = String.format("This did not throw the expected exception type (%s).  Instead, (%s) was thrown", myEx, ex);
56
                throw new TestFailureException(msg);
57
            }
58 2 1. assertThrows : negated conditional → KILLED
2. assertThrows : negated conditional → KILLED
            if (expectedMsg != null && !ex.getMessage().equals(expectedMsg)) {
59
                String msg = String.format("Did not get expected message (%s). Instead, got: %s", expectedMsg, ex.getMessage());
60
                throw new TestFailureException(msg);
61
            }
62 1 1. assertThrows : replaced return value with null for com/renomad/minum/testing/TestFramework::assertThrows → KILLED
            return (T) ex;
63
        }
64
65
    }
66
67
    /**
68
     * A helper for testing - assert two generics are equal.  If you
69
     * need to compare two byte arrays, see {@link #assertEqualByteArray(byte[], byte[])}
70
     */
71
    public static <T> void assertEquals(T left, T right) {
72 1 1. assertEquals : negated conditional → KILLED
        if (! left.equals(right)) {
73
            throw new TestFailureException("Not equal! %nleft:  %s %nright: %s".formatted(showWhiteSpace(left.toString()), showWhiteSpace(right.toString())));
74
        }
75
    }
76
77
    /**
78
     * Compares two byte arrays for equality
79
     */
80
    public static void assertEqualByteArray(byte[] left, byte[] right) {
81 2 1. assertEqualByteArray : negated conditional → KILLED
2. assertEqualByteArray : negated conditional → KILLED
        if (left == null || right == null) throw new TestFailureException("at least one of the inputs was null: left: %s right: %s".formatted(Arrays.toString(left), Arrays.toString(right)));
82 1 1. assertEqualByteArray : negated conditional → KILLED
        if (left.length != right.length) throw new TestFailureException("Not equal! left length: %d right length: %d".formatted(left.length, right.length));
83 2 1. assertEqualByteArray : negated conditional → KILLED
2. assertEqualByteArray : changed conditional boundary → KILLED
        for (int i = 0; i < left.length; i++) {
84 1 1. assertEqualByteArray : negated conditional → KILLED
            if (left[i] != right[i]) throw new TestFailureException("Not equal! at index %d left was: %d right was: %d".formatted(i, left[i], right[i]));
85
        }
86
    }
87
88
    public static void assertEqualByteArray(byte[] left, byte[] right, String failureMessage) {
89
        try {
90 1 1. assertEqualByteArray : removed call to com/renomad/minum/testing/TestFramework::assertEqualByteArray → KILLED
            assertEqualByteArray(left, right);
91
        } catch (TestFailureException ex) {
92
            throw new TestFailureException(ex.getMessage() + ". " + failureMessage);
93
        }
94
    }
95
96
    /**
97
     * asserts two lists are equal, ignoring the order.
98
     * For example, (a, b) is equal to (b, a)
99
     * <p>
100
     * Note that the lists must be of comparable objects, or else
101
     * a ClassCastException will be thrown
102
     */
103
    public static void assertEqualsDisregardOrder(List<? extends CharSequence> left, List<? extends CharSequence> right) {
104 1 1. assertEqualsDisregardOrder : negated conditional → KILLED
        if (left.size() != right.size()) {
105
            throw new TestFailureException(String.format("different sizes: left was %d, right was %d%n", left.size(), right.size()));
106
        }
107
        List<? extends CharSequence> orderedLeft = left.stream().sorted().toList();
108
        List<? extends CharSequence> orderedRight = right.stream().sorted().toList();
109
110 2 1. assertEqualsDisregardOrder : changed conditional boundary → KILLED
2. assertEqualsDisregardOrder : negated conditional → KILLED
        for (int i = 0; i < left.size(); i++) {
111 1 1. assertEqualsDisregardOrder : negated conditional → KILLED
            if (!orderedLeft.get(i).toString().contentEquals(orderedRight.get(i))) {
112
                throw new TestFailureException(
113
                        String.format(
114
                                "%n%ndifferent values:%n%nleft:  %s%nright: %s%n%nfull left:%n-----------%n%s%n%nfull right:%n-----------%n%s%n",
115
                                orderedLeft.get(i),
116
                                orderedRight.get(i),
117
                                String.join("\n", showWhiteSpace(left.toString())),
118
                                String.join("\n", showWhiteSpace(right.toString()))));
119
            }
120
        }
121
    }
122
123
    public static void assertEqualsDisregardOrder(List<? extends CharSequence> left, List<? extends CharSequence> right, String failureMessage) {
124
        try {
125 1 1. assertEqualsDisregardOrder : removed call to com/renomad/minum/testing/TestFramework::assertEqualsDisregardOrder → KILLED
            assertEqualsDisregardOrder(left, right);
126
        } catch (TestFailureException ex) {
127
            throw new TestFailureException(ex.getMessage() + ". " + failureMessage);
128
        }
129
    }
130
131
    /**
132
     * asserts that two lists are equal in value and order.
133
     * <br><br>
134
     * For example, (a, b) is equal to (a, b)
135
     * Does not expect null as an input value.
136
     * Two empty lists are considered equal.
137
     */
138
    public static <T> void assertEquals(List<T> left, List<T> right) {
139 1 1. assertEquals : removed call to com/renomad/minum/testing/TestFramework::assertEquals → KILLED
       assertEquals(left, right, "");
140
    }
141
142
    /**
143
     * asserts that two lists are equal in value and order.
144
     * <br><br>
145
     * For example, (a, b) is equal to (a, b)
146
     * Does not expect null as an input value.
147
     * Two empty lists are considered equal.
148
     * <br><br>
149
     * @param failureMessage a failureMessage that should be shown if this assertion fails
150
     */
151
    public static <T> void assertEquals(List<T> left, List<T> right, String failureMessage) {
152 1 1. assertEquals : negated conditional → KILLED
        if (left.size() != right.size()) {
153
            throw new TestFailureException(
154
                    String.format("different sizes: left was %d, right was %d. %s", left.size(), right.size(), failureMessage));
155
        }
156 2 1. assertEquals : negated conditional → KILLED
2. assertEquals : changed conditional boundary → KILLED
        for (int i = 0; i < left.size(); i++) {
157 1 1. assertEquals : negated conditional → KILLED
            if (!left.get(i).equals(right.get(i))) {
158
                throw new TestFailureException(
159
                        String.format("different values - left: \"%s\" right: \"%s\". %s", showWhiteSpace(left.get(i).toString()), showWhiteSpace(right.get(i).toString()), failureMessage));
160
            }
161
        }
162
    }
163
164
    public static void assertTrue(boolean value) {
165 1 1. assertTrue : removed call to com/renomad/minum/testing/TestFramework::assertTrue → KILLED
      assertTrue(value, "");
166
    }
167
168
    /**
169
     * Assert that something is true, and show a message if it fails. This
170
     * is also handy for including a kind of documentation in your test
171
     * code.  So, please carefully note this example of its use, because
172
     * there's a certain subtlety at play:
173
     * <p>
174
     *     <pre>
175
     *      {@code assertTrue(foo == true, "foo must be true");}
176
     *     </pre>
177
     * </p>
178
     * <p>
179
     * Notice something here: The message is a statement about what *should*
180
     * be true.  Sometimes, I see people who do it wrong here - they
181
     * add a message like *foo was wrong*, but that's a disconcerting
182
     * thing to see in a test.  Do it like the example above, instead.
183
     * </p>
184
     * <p>
185
     *     One other detail to mention: If this test fails, it doesn't really
186
     *     give you much help about what the value should have been, it merely
187
     *     insists it be true.  In some cases, like where you are
188
     *     asserting that a string contains a substring, it is handy to include
189
     *     what you were looking for and what the string was as part of the
190
     *     failure message.
191
     * </p>
192
     */
193
    public static void assertTrue(boolean value, String failureMessage) {
194 1 1. assertTrue : negated conditional → KILLED
        if (!value) {
195
            throw new TestFailureException(failureMessage);
196
        }
197
    }
198
199
    public static void assertFalse(boolean value) {
200 1 1. assertFalse : negated conditional → KILLED
        if (value) {
201
            throw new TestFailureException("value was unexpectedly true");
202
        }
203
    }
204
205
    public static void assertFalse(boolean value, String failureMessage) {
206 1 1. assertFalse : negated conditional → KILLED
        if (value) {
207
            throw new TestFailureException(failureMessage);
208
        }
209
    }
210
211
    /**
212
     * Given a string that may have whitespace chars,
213
     * render it in a way we can see.
214
     * <p>
215
     *     More specifically, it will replace tabs with (TAB),
216
     *     newlines with (NEWLINE), carriage returns with (RETURN).
217
     *     Also, if the entire text is empty (it's got a 0 length), you'll
218
     *     get back (EMPTY), and if blank (it's full of whitespace),
219
     *     you'll get back (BLANK).
220
     * </p>
221
     * <p>
222
     *     Note that this method is not very performant.  It carries out
223
     *     its work through multiple string replacements, so it's
224
     *     basically O(3*n) (that is, it scans through
225
     *     the whole string three times).
226
     * </p>
227
     */
228
    static String showWhiteSpace(String msg) {
229 2 1. showWhiteSpace : replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED
2. showWhiteSpace : negated conditional → KILLED
        if (msg == null) return "(NULL)";
230 2 1. showWhiteSpace : replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED
2. showWhiteSpace : negated conditional → KILLED
        if (msg.isEmpty()) return "(EMPTY)";
231
232
        // if we have tabs, returns, newlines in the text, show them
233
        String text = msg
234
                .replace("\t", "\\t")
235
                .replace("\r", "\\r")
236
                .replace("\n", "\\n");
237
238 2 1. showWhiteSpace : replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED
2. showWhiteSpace : negated conditional → KILLED
        if (text.isBlank()) return "(BLANK)";
239 1 1. showWhiteSpace : replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED
        return text;
240
    }
241
242
    /**
243
     * This builds a context very similarly to {@link FullSystem#buildContext()},
244
     * except that it uses {@link TestLogger} instead of {@link com.renomad.minum.logging.Logger}
245
     * @param loggerName this will assign a human-readable name to the logger's
246
     *                   LoggingActionQueue so we can distinguish it
247
     *                   when reviewing the threads
248
     * @see #buildTestingContext(String, Properties)
249
     * @see #shutdownTestingContext(Context)
250
     */
251
    public static Context buildTestingContext(String loggerName) {
252 1 1. buildTestingContext : replaced return value with null for com/renomad/minum/testing/TestFramework::buildTestingContext → KILLED
        return buildTestingContext(loggerName, null);
253
    }
254
255
    /**
256
     * This builds a context very similarly to {@link FullSystem#buildContext()},
257
     * except that it uses {@link TestLogger} instead of {@link com.renomad.minum.logging.Logger}.
258
     * @param loggerName this will assign a human-readable name to the logger's
259
     *                   LoggingActionQueue so we can distinguish it
260
     *                   when reviewing the threads
261
     * @param properties If you want, you can inject a properties object here, to have
262
     *                   greater control over your test.  Using a parameter of null here
263
     *                   will cause the system to obtain properties from the minum.config file
264
     * @see #shutdownTestingContext(Context)
265
     */
266
    public static Context buildTestingContext(String loggerName, Properties properties) {
267
        var constants = new Constants(properties);
268
        var executorService = Executors.newVirtualThreadPerTaskExecutor();
269
        var logger = new TestLogger(constants, executorService, loggerName);
270
271
        var context = new Context(executorService, constants);
272
273 1 1. buildTestingContext : removed call to com/renomad/minum/state/Context::setLogger → KILLED
        context.setLogger(logger);
274
275 1 1. buildTestingContext : replaced return value with null for com/renomad/minum/testing/TestFramework::buildTestingContext → KILLED
        return context;
276
    }
277
278
    /**
279
     * A helper to close down resources that are opened up by running
280
     * the {@link #buildTestingContext} methods.
281
     */
282
    public static void shutdownTestingContext(Context context) {
283 1 1. shutdownTestingContext : removed call to com/renomad/minum/queue/ActionQueueKiller::killAllQueues → KILLED
            new ActionQueueKiller(context).killAllQueues();
284
            context.getLogger().stop();
285
            context.getExecutorService().shutdownNow();
286
    }
287
288
}

Mutations

43

1.1
Location : assertThrows
Killed by : com.renomad.minum.utils.InvariantsTests.test_MustBeTrue(com.renomad.minum.utils.InvariantsTests)
replaced return value with null for com/renomad/minum/testing/TestFramework::assertThrows → KILLED

51

1.1
Location : assertThrows
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_RightIsNull(com.renomad.minum.testing.TestFrameworkTests)
removed call to com/renomad/minum/utils/ThrowingRunnable::run → KILLED

54

1.1
Location : assertThrows
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_RightIsNull(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

58

1.1
Location : assertThrows
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_RightIsNull(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

2.2
Location : assertThrows
Killed by : com.renomad.minum.utils.InvariantsTests.test_MustBeTrue(com.renomad.minum.utils.InvariantsTests)
negated conditional → KILLED

62

1.1
Location : assertThrows
Killed by : com.renomad.minum.utils.InvariantsTests.test_MustBeTrue(com.renomad.minum.utils.InvariantsTests)
replaced return value with null for com/renomad/minum/testing/TestFramework::assertThrows → KILLED

72

1.1
Location : assertEquals
Killed by : com.renomad.minum.state.ConstantsTests.testGettingConfiguredPropertiesFromFile_NothingFound(com.renomad.minum.state.ConstantsTests)
negated conditional → KILLED

81

1.1
Location : assertEqualByteArray
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_CustomError_ButValidComparison(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

2.2
Location : assertEqualByteArray
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_CustomError_ButValidComparison(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

82

1.1
Location : assertEqualByteArray
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_CustomError_ButValidComparison(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

83

1.1
Location : assertEqualByteArray
Killed by : com.renomad.minum.utils.ByteUtilsTests.testConversionToArray(com.renomad.minum.utils.ByteUtilsTests)
negated conditional → KILLED

2.2
Location : assertEqualByteArray
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_CustomError_ButValidComparison(com.renomad.minum.testing.TestFrameworkTests)
changed conditional boundary → KILLED

84

1.1
Location : assertEqualByteArray
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_CustomError_ButValidComparison(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

90

1.1
Location : assertEqualByteArray
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEqualsByteArray_CustomError(com.renomad.minum.testing.TestFrameworkTests)
removed call to com/renomad/minum/testing/TestFramework::assertEqualByteArray → KILLED

104

1.1
Location : assertEqualsDisregardOrder
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEquals_ListsDifferentOrders(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

110

1.1
Location : assertEqualsDisregardOrder
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEquals_ListsDifferentOrders(com.renomad.minum.testing.TestFrameworkTests)
changed conditional boundary → KILLED

2.2
Location : assertEqualsDisregardOrder
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEquals_ListsDifferentOrders(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

111

1.1
Location : assertEqualsDisregardOrder
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEquals_ListsDifferentOrders(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

125

1.1
Location : assertEqualsDisregardOrder
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertEquals_ListsDifferentOrders(com.renomad.minum.testing.TestFrameworkTests)
removed call to com/renomad/minum/testing/TestFramework::assertEqualsDisregardOrder → KILLED

139

1.1
Location : assertEquals
Killed by : com.renomad.minum.web.WebTests
removed call to com/renomad/minum/testing/TestFramework::assertEquals → KILLED

152

1.1
Location : assertEquals
Killed by : com.renomad.minum.state.ConstantsTests.testGetProps_Array(com.renomad.minum.state.ConstantsTests)
negated conditional → KILLED

156

1.1
Location : assertEquals
Killed by : com.renomad.minum.htmlparsing.HtmlParseNodeTests.testHappyPath(com.renomad.minum.htmlparsing.HtmlParseNodeTests)
negated conditional → KILLED

2.2
Location : assertEquals
Killed by : com.renomad.minum.state.ConstantsTests.testGetProps_Array(com.renomad.minum.state.ConstantsTests)
changed conditional boundary → KILLED

157

1.1
Location : assertEquals
Killed by : com.renomad.minum.state.ConstantsTests.testGetProps_Array(com.renomad.minum.state.ConstantsTests)
negated conditional → KILLED

165

1.1
Location : assertTrue
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/testing/TestFramework::assertTrue → KILLED

194

1.1
Location : assertTrue
Killed by : com.renomad.minum.utils.MyThreadTests.test_InterruptionHandler(com.renomad.minum.utils.MyThreadTests)
negated conditional → KILLED

200

1.1
Location : assertFalse
Killed by : com.renomad.minum.testing.RegexUtilsTests.test_isFound(com.renomad.minum.testing.RegexUtilsTests)
negated conditional → KILLED

206

1.1
Location : assertFalse
Killed by : com.renomad.minum.testing.TestFrameworkTests.test_assertFalse_WithMessage(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

229

1.1
Location : showWhiteSpace
Killed by : com.renomad.minum.testing.TestFrameworkTests.testShowWhiteSpace(com.renomad.minum.testing.TestFrameworkTests)
replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED

2.2
Location : showWhiteSpace
Killed by : com.renomad.minum.testing.TestFrameworkTests.testShowWhiteSpace(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

230

1.1
Location : showWhiteSpace
Killed by : com.renomad.minum.testing.TestFrameworkTests.testShowWhiteSpace(com.renomad.minum.testing.TestFrameworkTests)
replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED

2.2
Location : showWhiteSpace
Killed by : com.renomad.minum.testing.TestFrameworkTests.testShowWhiteSpace(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

238

1.1
Location : showWhiteSpace
Killed by : com.renomad.minum.testing.TestFrameworkTests.testShowWhiteSpace(com.renomad.minum.testing.TestFrameworkTests)
replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED

2.2
Location : showWhiteSpace
Killed by : com.renomad.minum.testing.TestFrameworkTests.testShowWhiteSpace(com.renomad.minum.testing.TestFrameworkTests)
negated conditional → KILLED

239

1.1
Location : showWhiteSpace
Killed by : com.renomad.minum.testing.TestFrameworkTests.testShowWhiteSpace(com.renomad.minum.testing.TestFrameworkTests)
replaced return value with "" for com/renomad/minum/testing/TestFramework::showWhiteSpace → KILLED

252

1.1
Location : buildTestingContext
Killed by : com.renomad.minum.logging.TestLoggerTests.test_findFirstMessage_CheckValidity_TooMany(com.renomad.minum.logging.TestLoggerTests)
replaced return value with null for com/renomad/minum/testing/TestFramework::buildTestingContext → KILLED

273

1.1
Location : buildTestingContext
Killed by : com.renomad.minum.logging.TestLoggerTests.test_TestLogger_MaxLines(com.renomad.minum.logging.TestLoggerTests)
removed call to com/renomad/minum/state/Context::setLogger → KILLED

275

1.1
Location : buildTestingContext
Killed by : com.renomad.minum.logging.TestLoggerTests.test_findFirstMessage_CheckValidity_TooMany(com.renomad.minum.logging.TestLoggerTests)
replaced return value with null for com/renomad/minum/testing/TestFramework::buildTestingContext → KILLED

283

1.1
Location : shutdownTestingContext
Killed by : com.renomad.minum.web.WebTests
removed call to com/renomad/minum/queue/ActionQueueKiller::killAllQueues → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0