NoCheatPlus/NCPCore/src/test/java/fr/neatmonster/nocheatplus/test/TestRegistrationOrder.java

375 lines
14 KiB
Java

/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.test;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.junit.Test;
import fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder;
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder;
import fr.neatmonster.nocheatplus.utilities.StringUtil;
import fr.neatmonster.nocheatplus.utilities.build.BuildParameters;
public class TestRegistrationOrder {
public static interface IAccessSort<F> {
public LinkedList<F> getSortedLinkedList(Collection<F> items);
public RegistrationOrder getRegistrationOrder(F item);
}
public static final IAccessSort<RegistrationOrder> accessRegistrationOrder = new IAccessSort<RegistrationOrder>() {
@Override
public LinkedList<RegistrationOrder> getSortedLinkedList(Collection<RegistrationOrder> items) {
return RegistrationOrder.sortRegistrationOrder.getSortedLinkedList(items);
}
@Override
public RegistrationOrder getRegistrationOrder(RegistrationOrder item) {
return item;
}
};
public static final class AccessIGetRegistrationOrder implements IAccessSort<IGetRegistrationOrder> {
@Override
public LinkedList<IGetRegistrationOrder> getSortedLinkedList(Collection<IGetRegistrationOrder> items) {
return RegistrationOrder.sortIGetRegistrationOrder.getSortedLinkedList(items);
}
@Override
public RegistrationOrder getRegistrationOrder(IGetRegistrationOrder item) {
return item.getRegistrationOrder();
}
}
public static final IAccessSort<IGetRegistrationOrder> accessIGetRegistrationOrder = new AccessIGetRegistrationOrder();
private static String[] tags = new String[]{null, "foo", "bar", "low", "high", "ledge", "bravo", "bravissimo"};
private final Random random = new Random(System.currentTimeMillis() ^ System.nanoTime());
@Test
public void testRegistrationOrder() {
int repeatShuffle = 15;
int rand1samples = BuildParameters.testLevel > 0 ? 500 : 50;
final List<List<RegistrationOrder>> samples = new ArrayList<List<RegistrationOrder>>();
// TODO: Hand crafted, random, part-random (defined tags, random order).
for (int i = 0; i < rand1samples; i++) {
samples.add(getSample(5, 12)); // Random-ish tests to have something in place.
}
// Iterate ...
for (List<RegistrationOrder> sample : samples) {
List<IGetRegistrationOrder> sample2 = new ArrayList<IGetRegistrationOrder>();
for (final RegistrationOrder order : sample) {
sample2.add(new IGetRegistrationOrder() {
@Override
public RegistrationOrder getRegistrationOrder() {
return order;
}
});
}
// Test sorting each.
testSorting(sample, accessRegistrationOrder, repeatShuffle);
testSorting(sample2, accessIGetRegistrationOrder, repeatShuffle);
// Test equality.
testEquality(sample, accessRegistrationOrder, sample2, accessIGetRegistrationOrder, repeatShuffle);
}
// TODO: Create RegistrationOrder and IGetRegistrationOrder inputs equally.
// TODO: Run sorting tests per input, and testEquality.
}
/**
* Run sorting test with the given items (!). Inputs are copied to an ArrayList.
*
* @param items
* @param fetcher
*/
private <F> void testSorting(List<F> items, IAccessSort<F> fetcher, int repeatShuffle) {
// (1) Sorting can't be stable / result in the same in general.
List<F> originalItems = items;
items = new ArrayList<F>(items);
LinkedList<F> sorted;
for (int i = 0; i < 1 + repeatShuffle; i++) {
sorted = fetcher.getSortedLinkedList(items);
testIfContained(originalItems, sorted);
testIfSorted(sorted, fetcher);
//testEquality(sorted, fetcher, fetcher.getSortedLinkedList(originalItems), fetcher); // (1)
Collections.reverse(items);
// testEquality(sorted, fetcher, fetcher.getSortedLinkedList(items), fetcher); // (1)
sorted = fetcher.getSortedLinkedList(items);
testIfContained(originalItems, sorted);
testIfSorted(sorted, fetcher);
//testEquality(sorted, fetcher, fetcher.getSortedLinkedList(originalItems), fetcher); // (1)
// Finally shuffle.
shuffle2s(items);
}
}
private <F> void testIfSorted(List<F> items, IAccessSort<F> fetcher) {
// Test priority ordering and rough region order.
Integer lastPriority = null;
Integer maxPriority = null;
boolean priorityContained = false;
boolean nullShouldFollow = false;
for (final F item : items) {
RegistrationOrder order = fetcher.getRegistrationOrder(item);
Integer basePriority = order.getBasePriority();
if (basePriority == null) {
if (maxPriority == null) {
if (order.getBeforeTag() == null && order.getAfterTag() != null) {
nullShouldFollow = true;
// Assume the pair-comparison would catch wrongly sorted null entries on occasion.
}
}
else {
if (nullShouldFollow && order.getBeforeTag() != null) {
fail("Invalid null-basePriority entry after basePriority>0 region: beforeTag is set.");
}
//
if (maxPriority > 0) {
nullShouldFollow = true;
}
else if (maxPriority == 0) {
if (order.getBeforeTag() != null) {
fail("Invalid null-basePriority entry within 0-basePriority region: beforeTag is set.");
}
}
}
}
else {
if (nullShouldFollow) {
fail("Invalid mixture of priority null/set after 0-basePriority region.");
}
else if (priorityContained && basePriority < 0 && lastPriority == null) {
fail("Invalid mixture of priority null/set before 0-basePriority region.");
}
if (maxPriority == null) {
maxPriority = basePriority;
}
else if (basePriority < maxPriority) {
fail("Order by priority broken.");
}
else {
maxPriority = basePriority;
}
priorityContained = true; // Remember to be able to exclude cases.
}
lastPriority = basePriority;
}
// Compare pairs, for being wrongly ordered.
for (int i = 1; i <items.size(); i++) {
// (Careful testing: greedy could apply both ways round.)
if (!RegistrationOrder.AbstractRegistrationOrderSort.shouldSortBefore(
fetcher.getRegistrationOrder(items.get(i - 1)),
fetcher.getRegistrationOrder(items.get(i)))) {
// Still test the other way round, to see if it's really demanded.
if (RegistrationOrder.AbstractRegistrationOrderSort.shouldSortBefore(
fetcher.getRegistrationOrder(items.get(i)),
fetcher.getRegistrationOrder(items.get(i - 1)))) {
// The following object is explicitly set to come before.
fail("Pair is wrongly ordered.");
}
// TODO: Could still consider checking i-1 versus following items, until explicit stop/end.
}
}
}
private <F> void testIfContained(List<F> originalItems, List<F> items) {
if (!new HashSet<F>(items).containsAll(originalItems)) {
fail("Sorted list does not contain all original items.");
}
}
/**
* Inputs are copied to an ArrayList.
*
* @param items1
* @param fetcher1
* @param items2
* @param fetcher2
*/
private <F1, F2> void testEquality(List<F1> items1, IAccessSort<F1> fetcher1,
List<F2> items2, IAccessSort<F2> fetcher2,
int repeatShuffle) {
// Test if sorting remains the same: original, and n times shuffled - input + reversed.
items1 = new ArrayList<F1>(items1);
items2 = new ArrayList<F2>(items2);
List<F1> sorted1;
List<F2> sorted2;
for (int i = 0; i < repeatShuffle + 1; i++) {
sorted1 = fetcher1.getSortedLinkedList(items1);
sorted2 = fetcher2.getSortedLinkedList(items2);
testEquality(sorted1, fetcher1, sorted2, fetcher2);
Collections.reverse(items1);
Collections.reverse(items2);
sorted1 = fetcher1.getSortedLinkedList(items1);
sorted2 = fetcher2.getSortedLinkedList(items2);
testEquality(sorted1, fetcher1, sorted2, fetcher2);
// In the end "shuffle" both inputs parallel.
int[][] swap = getSwapIndices(items1.size(), items1.size() * 2);
shuffle(items1, swap);
shuffle(items2, swap);
}
}
/**
* Create a somewhat regular randomized sample, having itemsPerLevel times
* items per priority level plus, plus a similar amount of null entries.<br>
* TODO: A version that guarantees every type is in?
*
* @param priorities
* @param itemsPerLevel
* @return
*/
private List<RegistrationOrder> getSample(int priorities, int itemsPerLevel) {
ArrayList<RegistrationOrder> out = new ArrayList<RegistrationOrder>();
int[] prioArr;
if (priorities <= 0) {
prioArr = new int[0];
}
else {
Set<Integer> prios = new LinkedHashSet<Integer>();
if (random.nextBoolean()) {
prios.add(0);
}
while (prios.size() < priorities) {
prios.add(random.nextInt(2 * priorities + 1) - priorities);
}
prioArr = new int[priorities];
int index = 0;
for (Integer x : prios) {
prioArr[index] = x;
index ++;
}
}
for (int i = 0; i < prioArr.length; i++) {
for (int x = 0; x < itemsPerLevel; x++) {
out.add(getRegistrationOrder(prioArr[i]));
}
}
for (int i = 0; i < itemsPerLevel; i++) {
out.add(getRegistrationOrder(null));
}
shuffle2s(out); // Extra shuffle to potentially unlink.
return out;
}
/**
* Get one using the default tags, beforeTag and afterTag get set to null, 1 2 or 3 others.
* @param priority
* @return
*/
private RegistrationOrder getRegistrationOrder(Integer basePriority) {
String tag = tags[random.nextInt(tags.length)];
String afterTag = random.nextBoolean() ? null : getTagRegex(random.nextInt(3) + 1);
String beforeTag = random.nextBoolean() ? null : getTagRegex(random.nextInt(3) + 1);
// StaticLog.logInfo("RegistrationOrder(b " + basePriority + " t " + tag + " bt " + beforeTag + " at " + afterTag + ")");
return new RegistrationOrder(basePriority, tag, beforeTag, afterTag);
}
/**
* Get a non null regex using default tags.
* @param combinations
*/
private String getTagRegex(int combinations) {
Set<String> indices = new HashSet<String>();
while (indices.size() < combinations) {
indices.add(tags[1 + random.nextInt(tags.length - 1)]); // Avoid null here.
}
// Combine tags to a regex (simple).
return"(" + StringUtil.join(indices, "|") + ")";
}
private <F1, F2> void testEquality(List<F1> items1, IAccessSort<F1> fetcher1,
List<F2> items2, IAccessSort<F2> fetcher2) {
for (int i = 0; i < items1.size(); i++) {
if (!fetcher1.getRegistrationOrder(items1.get(i)).equals(fetcher2.getRegistrationOrder(items2.get(i)))) {
// TODO: Return / log outside to give context.
doFail("Lists are not the same");
}
}
}
/**
* Shuffle by swapping items according to the give indices.
*
* @param items
* @param swap
* int[N][2], containing valid indices for items.
*/
private <F> void shuffle(List<F> items, int[][] swap) {
for (int x = 0; x < swap.length; x++) {
F temp1 = items.get(swap[x][1]);
items.set(swap[x][1], items.get(swap[x][0]));
items.set(swap[x][0], temp1);
}
}
/**
* Shuffle by swapping 2 times the list size items.
*
* @param items
*/
private <F> void shuffle2s(List<F> items) {
shuffle(items, 2 * items.size());
}
private <F> void shuffle(List<F> items, int n) {
int[][] swap = getSwapIndices(items.size(), n);
for (int x = 0; x < swap.length; x++) {
F temp1 = items.get(swap[x][1]);
items.set(swap[x][1], items.get(swap[x][0]));
items.set(swap[x][0], temp1);
}
}
/**
* Used for "shuffling".
* @param upperBound Excluded.
* @param n
* @return
*/
private int[][] getSwapIndices(int upperBound, int n) {
int[][] out = new int[n][2];
for (int i = 0; i < n; i++) {
out[i][0] = random.nextInt(upperBound);
out[i][1] = random.nextInt(upperBound);
}
return out;
}
private void doFail(String message) {
// TODO: Args.
fail(message);
}
}