AbstractDb.java

1
package com.renomad.minum.database;
2
3
import com.renomad.minum.logging.ILogger;
4
import com.renomad.minum.state.Context;
5
import com.renomad.minum.utils.IFileUtils;
6
7
import java.io.IOException;
8
import java.nio.file.Path;
9
import java.util.*;
10
import java.util.concurrent.Callable;
11
import java.util.concurrent.ConcurrentHashMap;
12
import java.util.concurrent.atomic.AtomicLong;
13
import java.util.concurrent.locks.ReentrantLock;
14
import java.util.function.Function;
15
16
/**
17
 * The abstract database class is a representation of the essential capabilities of
18
 * a Minum database.
19
 * <p>
20
 *     There are two kinds of database provided, which only differ in how they
21
 *     store data on disk.  The "classic" kind, {@link Db}, stores each piece of
22
 *     data in its own file.  This is the simplest approach.
23
 * </p>
24
 * <p>
25
 *     However, for significant speed gains, the new {@link DbEngine2} will
26
 *     store each change as an append to a file, and will consolidate the on-disk
27
 *     data occasionally, and on start.  That way is thousands of times faster
28
 *     to write to disk and to read from disk at startup.
29
 * </p>
30
 * @param <T> This is the type of data, which is always an implementation of
31
 *           the {@link DbData} class.  See the code of {@link com.renomad.minum.security.Inmate}
32
 *           for an example of how this should look.
33
 */
34
public abstract class AbstractDb<T extends DbData<?>> {
35
36
    /**
37
     * The directory of the database on disk
38
     */
39
    protected final Path dbDirectory;
40
41
    /**
42
     * An empty instance of the type of data stored by this
43
     * database, used for better handling of generics.
44
     */
45
    protected final T emptyInstance;
46
47
    /**
48
     * Used for handling some file utilities in the database like creating directories
49
     */
50
    protected final IFileUtils fileUtils;
51
52
    /**
53
     * Holds some system-wide information that is beneficial for components of the database
54
     */
55
    protected final Context context;
56
57
    /**
58
     * Used for providing logging throughout the database
59
     */
60
    protected final ILogger logger;
61
62
    /**
63
     * The internal data structure of the database that resides in memory.  The beating heart
64
     * of the database while it runs.
65
     */
66
    protected final Map<Long, T> data;
67
68
    /**
69
     * used to place locks around certain actions that need to avoid
70
     * thread interleaving.
71
     */
72
    private final ReentrantLock dbLock;
73
74
    /**
75
     * The current index, used when creating new data items.  Each item has its own
76
     * index value, this is where it is tracked.
77
     */
78
    protected AtomicLong index;
79
80
    // components for registered indexes (for faster read performance)
81
82
    /**
83
     * This data structure is a nested map used for providing indexed data search.
84
     * <br>
85
     * The outer map is between the name of the index and the inner map.
86
     * <br>
87
     * The inner map is between strings and sets of items related to that string.
88
     */
89
    protected final Map<String, Map<String, Set<T>>> registeredIndexes;
90
91
    /**
92
     * This map holds the functions that are registered to indexes, which are used
93
     * to construct the mappings between string values and items in the database.
94
     */
95
    protected final Map<String, Function<T, String>> partitioningMap;
96
97
    protected AbstractDb(Path dbDirectory, Context context, T instance, IFileUtils fileUtils) {
98
        this.dbDirectory = dbDirectory;
99
        this.context = context;
100
        this.emptyInstance = instance;
101
        this.data = new ConcurrentHashMap<>();
102
        this.logger = context.getLogger();
103
        this.registeredIndexes = new HashMap<>();
104
        this.partitioningMap = new HashMap<>();
105
        this.fileUtils = fileUtils;
106
        this.dbLock = new ReentrantLock();
107
    }
108
109
    /**
110
     * Used to cleanly stop the database.
111
     * <br>
112
     * In the case of {@link Db} this will interrupt its internal queue and tell it
113
     * to finish up processing.
114
     * <br>
115
     * In the case of {@link DbEngine2} this will flush data to disk.
116
     */
117
    public abstract void stop() throws IOException;
118
119
    /**
120
     * Used to cleanly stop the database, with extra allowance of time
121
     * for cleanup.
122
     * <br>
123
     * Note that this method mostly applies to {@link Db}, and not as much
124
     * to {@link DbEngine2}.  Only Db uses a processing queue on a thread which
125
     * is what requires a longer shutdown time for interruption.
126
     * @param count number of loops before we are done waiting for a clean close
127
     *              and instead crash the instance closed.
128
     * @param sleepTime how long to wait, in milliseconds, for each iteration of the waiting loop.
129
     */
130
    public abstract void stop(int count, int sleepTime) throws IOException;
131
132
133
    /**
134
     * Write data to the database.  Use an index of 0 to store new data, and a positive
135
     * non-zero value to update data.
136
     * <p><em>
137
     * Example of adding new data to the database:
138
     * </em></p>
139
     * {@snippet :
140
     *          final var newSalt = StringUtils.generateSecureRandomString(10);
141
     *          final var hashedPassword = CryptoUtils.createPasswordHash(newPassword, newSalt);
142
     *          final var newUser = new User(0L, newUsername, hashedPassword, newSalt);
143
     *          userDb.write(newUser);
144
     * }
145
     * <p><em>
146
     * Example of updating data:
147
     * </em></p>
148
     * {@snippet :
149
     *         // write the updated salted password to the database
150
     *         final var updatedUser = new User(
151
     *                 user().getIndex(),
152
     *                 user().getUsername(),
153
     *                 hashedPassword,
154
     *                 newSalt);
155
     *         userDb.write(updatedUser);
156
     * }
157
     *
158
     * @param newData the data we are writing
159
     * @return the data with its new index assigned.
160
     */
161
    public abstract T write(T newData);
162
163
    /**
164
     * Write database data into memory
165
     * @param newData the new data may be totally new or an update
166
     * @param newElementCreated if true, this is a create.  If false, an update.
167
     */
168
    protected void writeToMemory(T newData, boolean newElementCreated) {
169
        // if we got here, we are safe to proceed with putting the data into memory and disk
170
        logger.logTrace(() -> String.format("in thread %s, writing data %s", Thread.currentThread().getName(), newData));
171
        T oldData = data.put(newData.getIndex(), newData);
172
173
        // handle the indexes differently depending on whether this is a create or delete
174 1 1. writeToMemory : negated conditional → KILLED
        if (newElementCreated) {
175 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED
            addToIndexes(newData);
176
        } else {
177 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED
            removeFromIndexes(oldData);
178 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED
            addToIndexes(newData);
179
        }
180
    }
181
182
    /**
183
     * When new data comes in, we look at its "index" value. If
184
     * it is zero, it's a create, and we assign it a new value.  If it is
185
     * positive, it is an update, and we had better find it in the database
186
     * already, or else throw an exception.
187
     * @return true if a create, false if an update
188
     */
189
    protected boolean processDataIndex(T newData) {
190
        // *** deal with the in-memory portion ***
191
        boolean newElementCreated = false;
192
        // create a new index for the data, if needed
193 1 1. processDataIndex : negated conditional → KILLED
        if (newData.getIndex() == 0) {
194 1 1. processDataIndex : removed call to com/renomad/minum/database/DbData::setIndex → KILLED
            newData.setIndex(index.getAndIncrement());
195
            newElementCreated = true;
196
        } else {
197
            // if the data does not exist, and a positive non-zero
198
            // index was provided, throw an exception.
199
            boolean dataEntryExists = data.containsKey(newData.getIndex());
200 1 1. processDataIndex : negated conditional → KILLED
            if (!dataEntryExists) {
201
                throw new DbException(
202
                        String.format("Positive indexes are only allowed when updating existing data. Index: %d",
203
                                newData.getIndex()));
204
            }
205
        }
206 2 1. processDataIndex : replaced boolean return with false for com/renomad/minum/database/AbstractDb::processDataIndex → TIMED_OUT
2. processDataIndex : replaced boolean return with true for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED
        return newElementCreated;
207
    }
208
209
    /**
210
     * Delete data
211
     * <p><em>Example:</em></p>
212
     * {@snippet :
213
     *      userDb.delete(user);
214
     * }
215
     *
216
     * @param dataToDelete the data we are serializing and writing
217
     */
218
    public abstract void delete(T dataToDelete);
219
220
221
    /**
222
     * Remove a particular item from the internal data structure in memory
223
     */
224
    protected void deleteFromMemory(T dataToDelete) {
225
        long dataIndex;
226 1 1. deleteFromMemory : negated conditional → KILLED
        if (dataToDelete == null) {
227
            throw new DbException("Invalid to be given a null value to delete");
228
        }
229
        dataIndex = dataToDelete.getIndex();
230 1 1. deleteFromMemory : negated conditional → TIMED_OUT
        if (!data.containsKey(dataIndex)) {
231
            throw new DbException("no data was found with index of " + dataIndex);
232
        }
233
        long finalDataIndex = dataIndex;
234
        logger.logTrace(() -> String.format("in thread %s, deleting data with index %d", Thread.currentThread().getName(), finalDataIndex));
235
        data.remove(dataIndex);
236 1 1. deleteFromMemory : removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → TIMED_OUT
        removeFromIndexes(dataToDelete);
237
238
        // if all the data was just now deleted, we need to
239
        // reset the index back to 1
240 1 1. deleteFromMemory : negated conditional → KILLED
        if (data.isEmpty()) {
241 1 1. deleteFromMemory : removed call to java/util/concurrent/atomic/AtomicLong::set → KILLED
            index.set(1);
242
        }
243
    }
244
245
246
    /**
247
     *  add the data to registered indexes.
248
     *  <br>
249
     *  For each of the registered indexes,
250
     *  get the stored function to obtain a string value which helps divide
251
     *  the overall data into partitions.
252
     */
253
    protected void addToIndexes(T dbData) {
254
255
        for (var entry : partitioningMap.entrySet()) {
256
            // a function provided by the user to obtain an index-key: a unique or semi-unique
257
            // value to help partition / index the data
258
            Function<T, String> indexStringFunction = entry.getValue();
259
            String propertyAsString = indexStringFunction.apply(dbData);
260
            Map<String, Set<T>> stringIndexMap = registeredIndexes.get(entry.getKey());
261 1 1. addToIndexes : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
            dbLock.lock();
262
            try {
263 1 1. lambda$addToIndexes$2 : replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::lambda$addToIndexes$2 → KILLED
                stringIndexMap.computeIfAbsent(propertyAsString, k -> new HashSet<>());
264
                // if the index-key provides a 1-to-1 mapping to items, like UUIDs, then
265
                // each value will have only one item in the collection.  In other cases,
266
                // like when partitioning the data into multiple groups, there could easily
267
                // be many items per index value.
268
                Set<T> dataSet = stringIndexMap.get(propertyAsString);
269
                dataSet.add(dbData);
270
            } finally {
271 1 1. addToIndexes : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
                dbLock.unlock();
272
            }
273
        }
274
    }
275
276
    /**
277
     * Run when an item is deleted from the database
278
     */
279
    private void removeFromIndexes(T dbData) {
280
        for (var entry : partitioningMap.entrySet()) {
281
            // a function provided by the user to obtain an index-key: a unique or semi-unique
282
            // value to help partition / index the data
283
            Function<T, String> indexStringFunction = entry.getValue();
284
            String propertyAsString = indexStringFunction.apply(dbData);
285
            Map<String, Set<T>> stringIndexMap = registeredIndexes.get(entry.getKey());
286 1 1. removeFromIndexes : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
            dbLock.lock();
287
            try {
288 2 1. lambda$removeFromIndexes$3 : replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$removeFromIndexes$3 → KILLED
2. lambda$removeFromIndexes$3 : negated conditional → KILLED
                stringIndexMap.get(propertyAsString).removeIf(x -> x.getIndex() == dbData.getIndex());
289
290
                // in certain cases, we're removing one of the items that is indexed but
291
                // there are more left.  If there's nothing left though, we'll remove the mapping.
292 1 1. removeFromIndexes : negated conditional → TIMED_OUT
                if (stringIndexMap.get(propertyAsString).isEmpty()) {
293
                    stringIndexMap.remove(propertyAsString);
294
                }
295
            } finally {
296 1 1. removeFromIndexes : removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT
                dbLock.unlock();
297
            }
298
        }
299
    }
300
301
302
    /**
303
     * Cause the database to immediately load all its data.
304
     * <p>
305
     *     If this method is not invoked by the developer, then the data will be loaded
306
     *     lazily, at the first point where it is needed, such as when
307
     *     getting or writing data.
308
     * </p>
309
     * <p>
310
     *     It is a good idea to regularly use this method at database
311
     *     initialization.  By doing so, the check for data corruption issues will
312
     *     throw an exception that will cause the whole application to halt
313
     *     immediately, which is far preferable to having the exception show
314
     *     as a complaint in the logs.
315
     * </p>
316
     * <p>
317
     *     An example database initialization with bells and whistles
318
     *     is as follows:
319
     * </p>
320
     * <pre>
321
     *     {@code
322
     *     AbstractDb<PersonName> sampleDomainDb = context.getDb2("names", PersonName.EMPTY)
323
     *          .registerIndex("name_index", name -> name)
324
     *          .loadData();
325
     *     }
326
     * </pre>
327
     */
328
    public abstract AbstractDb<T> loadData();
329
330
    /**
331
     * This method returns a read-only view of the values of a database.
332
     *
333
     * <p><em>Example:</em></p>
334
     * {@snippet :
335
     * boolean doesUserAlreadyExist(String username) {
336
     *     return userDb.values().stream().anyMatch(x -> x.getUsername().equals(username));
337
     * }
338
     * }
339
     */
340
    public abstract Collection<T> values();
341
342
    /**
343
     * Register an index in the database for higher performance data access.
344
     * <p>
345
     *     This command should be run immediately after database declaration,
346
     *     or more specifically, before any data is loaded from disk. Otherwise,
347
     *     it would be possible to skip indexing that data.
348
     * </p>
349
     * <br>
350
     * Example:
351
     * <pre>
352
     *     {@code
353
     *      final var myDatabase = context.getDb("photos", Photograph.EMPTY)
354
     *               .registerIndex("url", photo -> photo.getUrl())
355
     *               .loadData();
356
     *     }
357
     * </pre>
358
     * @param indexName a string used to distinguish this index.  This string will be used again
359
     *                  when requesting data in a method like {@link #getIndexedData} or {@link #findExactlyOne}
360
     * @param keyObtainingFunction a function which obtains data from the data in this database, used
361
     *                             to partition the data into groups (potentially up to a 1-to-1 correspondence
362
     *                             between id and object)
363
     * @return the database instance if the registration succeeded
364
     * @throws DbException if the parameters are not entered properly, if the index has already
365
     * been registered, or if the data has already been loaded. It is necessary that
366
     * this is run immediately after declaring the database. To explain further: the data is not
367
     * actually loaded until the first time it is needed, such as running a write or delete, or
368
     * if the {@link #loadData()} ()} method is run.  Creating an index map for the data that
369
     * is read from disk only occurs once, at data load time.  Thus, it is crucial that the
370
     * registerIndex command is run before any data is loaded.
371
     */
372
    public AbstractDb<T> registerIndex(String indexName, Function<T, String> keyObtainingFunction) {
373 1 1. registerIndex : negated conditional → KILLED
        if (keyObtainingFunction == null) {
374
            throw new DbException("When registering an index, the partitioning algorithm must not be null");
375
        }
376 2 1. registerIndex : negated conditional → KILLED
2. registerIndex : negated conditional → KILLED
        if (indexName == null || indexName.isBlank()) {
377
            throw new DbException("When registering an index, value must be a non-empty string");
378
        }
379 1 1. registerIndex : negated conditional → KILLED
        if (registeredIndexes.containsKey(indexName)) {
380
            throw new DbException("It is forbidden to register the same index more than once.  Duplicate index: \""+indexName+"\"");
381
        }
382
        HashMap<String, Set<T>> stringCollectionHashMap = new HashMap<>();
383
        registeredIndexes.put(indexName, stringCollectionHashMap);
384
        partitioningMap.put(indexName, keyObtainingFunction);
385 1 1. registerIndex : replaced return value with null for com/renomad/minum/database/AbstractDb::registerIndex → KILLED
        return this;
386
    }
387
388
    /**
389
     * Given the name of a registered index (see {@link #registerIndex(String, Function)}),
390
     * use the key to find the collection of data that matches it.
391
     * @param indexName the name of an index
392
     * @param key a string value that matches a partition calculated from the partition
393
     *            function provided to {@link #registerIndex(String, Function)}
394
     * @return a collection of data, an empty collection if nothing found
395
     */
396
    public Collection<T> getIndexedData(String indexName, String key) {
397 1 1. getIndexedData : negated conditional → KILLED
        if (!registeredIndexes.containsKey(indexName)) {
398
            throw new DbException("There is no index registered on the database Db<"+this.emptyInstance.getClass().getSimpleName()+"> with a name of \""+indexName+"\"");
399
        }
400 1 1. getIndexedData : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        dbLock.lock();
401
        try {
402
            Set<T> ts = registeredIndexes.get(indexName).get(key);
403 1 1. getIndexedData : negated conditional → TIMED_OUT
            if (ts != null) {
404 1 1. getIndexedData : replaced return value with Collections.emptyList for com/renomad/minum/database/AbstractDb::getIndexedData → KILLED
                return new HashSet<>(ts);
405
            } else {
406
                return Set.of();
407
            }
408
        } finally {
409 1 1. getIndexedData : removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT
            dbLock.unlock();
410
        }
411
    }
412
413
    /**
414
     * Get a set of the currently-registered indexes on this database, useful
415
     * for debugging.
416
     */
417
    public Set<String> getSetOfIndexes() {
418 1 1. getSetOfIndexes : replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::getSetOfIndexes → KILLED
        return partitioningMap.keySet();
419
    }
420
421
    /**
422
     * A utility to find exactly one item from the database.
423
     * <br>
424
     * This utility will search the indexes for a particular data by
425
     * indexName and indexKey.  If not found, it will return null. If
426
     * found, it will be returned. If more than one are found, an exception
427
     * will be thrown.  Use this tool when the data has been uniquely
428
     * indexed, like for example when setting a unique identifier into
429
     * each data.
430
     * @param indexName the name of the index, an arbitrary value set by the
431
     *                  user to help distinguish among potentially many indexes
432
     *                  set on this data
433
     * @param indexKey the key for this particular value, such as a UUID or a name
434
     *                 or any other way to partition the data
435
     * @see #findExactlyOne(String, String, Callable)
436
     */
437
    public T findExactlyOne(String indexName, String indexKey) {
438 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → TIMED_OUT
        return findExactlyOne(indexName, indexKey, () -> null);
439
    }
440
441
    /**
442
     * Find one item, with an alternate value if nothing was found
443
     * <br>
444
     * This utility will search the indexes for a particular data by
445
     * indexName and indexKey.  If not found, it will return an alternate
446
     * value provided by the "alternate" parameter. If
447
     * found, it will be returned. If more than one are found, an exception
448
     * will be thrown.  Use this tool when the data has been uniquely
449
     * indexed, like for example when setting a unique identifier into
450
     * each data.
451
     * @param indexName the name of the index, an arbitrary value set by the
452
     *                  user to help distinguish among potentially many indexes
453
     *                  set on this data
454
     * @param indexKey the key for this particular value, such as a UUID or a name
455
     *                 or any other way to partition the data
456
     * @param alternate a functional interface that will be run if no result
457
     *                  was found
458
     * @see #findExactlyOne(String, String)
459
     */
460
    public T findExactlyOne(String indexName, String indexKey, Callable<T> alternate) {
461
        Collection<T> indexedData = getIndexedData(indexName, indexKey);
462 1 1. findExactlyOne : negated conditional → KILLED
        if (indexedData.isEmpty()) {
463
            try {
464 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → TIMED_OUT
                return alternate.call();
465
            } catch (Exception ex) {
466
                throw new DbException(ex);
467
            }
468 1 1. findExactlyOne : negated conditional → KILLED
        } else if (indexedData.size() == 1) {
469 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED
            return indexedData.stream().findFirst().orElseThrow();
470
        } else {
471
            throw new DbException("More than one item found when searching database Db<%s> on index \"%s\" with key %s"
472
                    .formatted(emptyInstance.getClass().getSimpleName(), indexName, indexKey));
473
        }
474
    }
475
}

Mutations

174

1.1
Location : writeToMemory
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

175

1.1
Location : writeToMemory
Killed by : com.renomad.minum.web.WebTests
removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED

177

1.1
Location : writeToMemory
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED

178

1.1
Location : writeToMemory
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED

193

1.1
Location : processDataIndex
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

194

1.1
Location : processDataIndex
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/database/DbData::setIndex → KILLED

200

1.1
Location : processDataIndex
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

206

1.1
Location : processDataIndex
Killed by : none
replaced boolean return with false for com/renomad/minum/database/AbstractDb::processDataIndex → TIMED_OUT

2.2
Location : processDataIndex
Killed by : com.renomad.minum.web.WebTests
replaced boolean return with true for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED

226

1.1
Location : deleteFromMemory
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

230

1.1
Location : deleteFromMemory
Killed by : none
negated conditional → TIMED_OUT

236

1.1
Location : deleteFromMemory
Killed by : none
removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → TIMED_OUT

240

1.1
Location : deleteFromMemory
Killed by : com.renomad.minum.database.DbEngine2Tests
negated conditional → KILLED

241

1.1
Location : deleteFromMemory
Killed by : com.renomad.minum.security.TheBrigTests
removed call to java/util/concurrent/atomic/AtomicLong::set → KILLED

261

1.1
Location : addToIndexes
Killed by : com.renomad.minum.web.WebTests
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

263

1.1
Location : lambda$addToIndexes$2
Killed by : com.renomad.minum.web.WebTests
replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::lambda$addToIndexes$2 → KILLED

271

1.1
Location : addToIndexes
Killed by : com.renomad.minum.web.WebTests
removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED

286

1.1
Location : removeFromIndexes
Killed by : com.renomad.minum.security.TheBrigTests
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

288

1.1
Location : lambda$removeFromIndexes$3
Killed by : com.renomad.minum.database.DbEngine2Tests
replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$removeFromIndexes$3 → KILLED

2.2
Location : lambda$removeFromIndexes$3
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

292

1.1
Location : removeFromIndexes
Killed by : none
negated conditional → TIMED_OUT

296

1.1
Location : removeFromIndexes
Killed by : none
removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT

373

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

376

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

2.2
Location : registerIndex
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

379

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

385

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
replaced return value with null for com/renomad/minum/database/AbstractDb::registerIndex → KILLED

397

1.1
Location : getIndexedData
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

400

1.1
Location : getIndexedData
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

403

1.1
Location : getIndexedData
Killed by : none
negated conditional → TIMED_OUT

404

1.1
Location : getIndexedData
Killed by : com.renomad.minum.web.WebTests
replaced return value with Collections.emptyList for com/renomad/minum/database/AbstractDb::getIndexedData → KILLED

409

1.1
Location : getIndexedData
Killed by : none
removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT

418

1.1
Location : getSetOfIndexes
Killed by : com.renomad.minum.database.DbEngine2Tests
replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::getSetOfIndexes → KILLED

438

1.1
Location : findExactlyOne
Killed by : none
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → TIMED_OUT

462

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

464

1.1
Location : findExactlyOne
Killed by : none
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → TIMED_OUT

468

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

469

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0