mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
Tests: Add deep recursive test of copy method (#10756)
* Add recursive test of copy * no need to print those unless debugging * WIP * comment debug print * more looping on copyable objects. * Allow for smarter recursion sub Cards * check that the special fields were encountered * better comments for fixing the copy errors
This commit is contained in:
parent
205f61703d
commit
461fe71314
1 changed files with 168 additions and 15 deletions
|
|
@ -2,10 +2,15 @@ package mage.verify;
|
|||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.AbilityImpl;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.common.*;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.FightTargetsEffect;
|
||||
import mage.abilities.effects.common.counter.ProliferateEffect;
|
||||
|
|
@ -16,9 +21,11 @@ import mage.cards.decks.CardNameUtil;
|
|||
import mage.cards.decks.DeckCardLists;
|
||||
import mage.cards.decks.importer.DeckImporter;
|
||||
import mage.cards.repository.*;
|
||||
import mage.choices.Choice;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Rarity;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.Filter;
|
||||
import mage.game.command.Dungeon;
|
||||
import mage.game.command.Plane;
|
||||
import mage.game.draft.DraftCube;
|
||||
|
|
@ -28,6 +35,7 @@ import mage.game.permanent.token.TokenImpl;
|
|||
import mage.game.permanent.token.custom.CreatureToken;
|
||||
import mage.server.util.SystemUtil;
|
||||
import mage.sets.TherosBeyondDeath;
|
||||
import mage.target.targetpointer.TargetPointer;
|
||||
import mage.util.CardUtil;
|
||||
import mage.verify.mtgjson.MtgJsonCard;
|
||||
import mage.verify.mtgjson.MtgJsonService;
|
||||
|
|
@ -41,10 +49,7 @@ import org.mage.plugins.card.dl.sources.ScryfallImageSupportCards;
|
|||
import org.reflections.Reflections;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
|
|
@ -1643,6 +1648,9 @@ public class VerifyCardDataTest {
|
|||
}
|
||||
}
|
||||
|
||||
// "copy" fails means that the copy constructor are not correct inside a card.
|
||||
// To fix those, try to find the class that did trigger the copy failure, and check
|
||||
// that copy() exists, a copy constructor exists, and the copy constructor is right.
|
||||
private void checkCardCanBeCopied(Card card1) {
|
||||
Card card2;
|
||||
try {
|
||||
|
|
@ -1652,18 +1660,163 @@ public class VerifyCardDataTest {
|
|||
return;
|
||||
}
|
||||
|
||||
// Checks that ability and effect are of the same class when copied.
|
||||
for (int i = 0; i < card1.getAbilities().size(); i++) {
|
||||
Ability ability1 = card1.getAbilities().get(i);
|
||||
Ability ability2 = card2.getAbilities().get(i);
|
||||
if (!ability1.getClass().equals(ability2.getClass())) {
|
||||
fail(card1, "copy", " miss copy in ability " + ability1.getClass().getName());
|
||||
compareClassRecursive(card1, card2, card1, "[Card", 10, new HashSet<>(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj1 first object to compare. Initially the original card.
|
||||
* @param obj2 second object to compare. Initially the copy of the original card.
|
||||
* @param originalCard the original card, used to print a nice message on fail.
|
||||
* @param msg the recursively built message to explain what is different.
|
||||
* @param maxDepth the maximum allowed recursion. A safety mesure for the test to end.
|
||||
* @param alreadyChecked Map of all Cards obj1 already compared.
|
||||
* @param useRecursive When false, do not recursively compare Cards.
|
||||
*/
|
||||
private void compareClassRecursive(Object obj1, Object obj2, Card originalCard, String msg, int maxDepth,
|
||||
Set<Card> alreadyChecked, boolean useRecursive) {
|
||||
if (obj1 == null && obj2 == null) {
|
||||
return;
|
||||
} else if (obj1 == null || obj2 == null) {
|
||||
fail(originalCard, "copy", "not same class for " + msg + "]");
|
||||
} else if (obj1.getClass() != obj2.getClass()) {
|
||||
fail(originalCard, "copy", "not same class for " + msg + "<" + obj1.getClass() + ">" + "]");
|
||||
} else if (obj1 == obj2) { // for instances mostly
|
||||
return;
|
||||
} else {
|
||||
// Only recurse so much.
|
||||
if (maxDepth == 0) {
|
||||
return;
|
||||
}
|
||||
for (int j = 0; j < ability1.getEffects().size(); j++) {
|
||||
Effect effect1 = ability1.getEffects().get(j);
|
||||
Effect effect2 = ability2.getEffects().get(j);
|
||||
if (!effect1.getClass().equals(effect2.getClass())) {
|
||||
fail(card1, "copy", "miss copy in effect " + effect1.getClass().getName());
|
||||
// Only recurse on those objects
|
||||
if (obj1 instanceof MageObject || obj1 instanceof Filter || obj1 instanceof Condition || obj1 instanceof Effect
|
||||
|| obj1 instanceof Ability || obj1 instanceof Mana || obj1 instanceof Cost || obj1 instanceof DynamicValue
|
||||
|| obj1 instanceof Choice || obj1 instanceof TargetPointer) {
|
||||
|
||||
boolean doRecurse = useRecursive;
|
||||
if (obj1 instanceof Card) {
|
||||
if (alreadyChecked.contains(obj1)) {
|
||||
if (!doRecurse) {
|
||||
return; // we already checked that obj1 and do not want to recurse. stop there.
|
||||
} else {
|
||||
doRecurse = false;
|
||||
}
|
||||
} else {
|
||||
alreadyChecked.add((Card) obj1);
|
||||
}
|
||||
}
|
||||
|
||||
//System.out.println(msg);
|
||||
Class class1 = obj1.getClass();
|
||||
Class class2 = obj2.getClass();
|
||||
do {
|
||||
if (class1 == null && class2 == null) {
|
||||
return;
|
||||
}
|
||||
if (class1 == null || class2 == null) {
|
||||
fail(originalCard, "copy", "not same class for " + msg + "<" + obj1.getClass() + ">" + "]");
|
||||
return;
|
||||
}
|
||||
List<Field> ability2Fields = Arrays.stream(class2.getDeclaredFields()).collect(Collectors.toList());
|
||||
|
||||
// Special fields for CardImpl.class
|
||||
boolean hasSpellAbilityField = false;
|
||||
boolean hasMeldField = false;
|
||||
boolean hasSecondSideCardField = false;
|
||||
// Special fields for AbilityImpl.class
|
||||
boolean hasWatchersField = false;
|
||||
boolean hasModesField = false;
|
||||
|
||||
int fieldIndex = 0;
|
||||
for (Field field1 : class1.getDeclaredFields()) {
|
||||
Field field2 = ability2Fields.get(fieldIndex);
|
||||
field1.setAccessible(true);
|
||||
field2.setAccessible(true);
|
||||
try {
|
||||
Object value1 = field1.get(obj1);
|
||||
Object value2 = field2.get(obj2);
|
||||
|
||||
boolean doFieldRecurse = true;
|
||||
if (class1 == CardImpl.class) {
|
||||
if (field1.getName() == "spellAbility") {
|
||||
compareClassRecursive(((CardImpl) obj1).getSpellAbility(), ((CardImpl) obj2).getSpellAbility(), originalCard, msg + "<" + obj1.getClass() + ">" + "::" + field1.getName(), maxDepth - 1, alreadyChecked, doRecurse);
|
||||
doFieldRecurse = false;
|
||||
hasSpellAbilityField = true;
|
||||
} else if (field1.getName() == "meldsToCard") {
|
||||
compareClassRecursive(((CardImpl) obj1).getMeldsToCard(), ((CardImpl) obj2).getMeldsToCard(), originalCard, msg + "::" + field1.getName(), maxDepth - 1, alreadyChecked, doRecurse);
|
||||
doFieldRecurse = false;
|
||||
hasMeldField = true;
|
||||
} else if (field1.getName() == "secondSideCard") {
|
||||
compareClassRecursive(((CardImpl) obj1).getSecondCardFace(), ((CardImpl) obj2).getSecondCardFace(), originalCard, msg + "::" + field1.getName(), maxDepth - 1, alreadyChecked, doRecurse);
|
||||
doFieldRecurse = false;
|
||||
hasSecondSideCardField = true;
|
||||
}
|
||||
}
|
||||
if (class1 == AbilityImpl.class) {
|
||||
if (field1.getName() == "watchers") {
|
||||
// Watchers are only used on initialization, they are not copied.
|
||||
doFieldRecurse = false;
|
||||
hasWatchersField = true;
|
||||
}
|
||||
if (field1.getName() == "modes") {
|
||||
//compareClassRecursive(((AbilityImpl) obj1).getModes(), ((AbilityImpl) obj2).getModes(), originalCard, msg + "<" + obj1.getClass() + ">" + "::" + field1.getName(), maxDepth - 1);
|
||||
compareClassRecursive(((AbilityImpl) obj1).getEffects(), ((AbilityImpl) obj2).getEffects(), originalCard, msg + "<" + obj1.getClass() + ">" + "::" + field1.getName(), maxDepth - 1, alreadyChecked, doRecurse);
|
||||
doFieldRecurse = false;
|
||||
hasModesField = true;
|
||||
}
|
||||
}
|
||||
if (doFieldRecurse) {
|
||||
compareClassRecursive(value1, value2, originalCard, msg + "<" + obj1.getClass() + ">" + "::" + field1.getName(), maxDepth - 1, alreadyChecked, doRecurse);
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
}
|
||||
fieldIndex++;
|
||||
}
|
||||
|
||||
// Do check that the expected special fields were encountered.
|
||||
// If those field are no relevant anymore, or were renamed, please modify the matching code
|
||||
// block above on how to loop into those fields.
|
||||
if (class1 == CardImpl.class) {
|
||||
if (!hasSpellAbilityField) {
|
||||
fail(originalCard, "copy", "was expecting a spellAbility field, but found none " + msg + "]");
|
||||
}
|
||||
if (!hasMeldField) {
|
||||
fail(originalCard, "copy", "was expecting a meldsToCard field, but found none " + msg + "]");
|
||||
}
|
||||
if (!hasSecondSideCardField) {
|
||||
fail(originalCard, "copy", "was expecting a secondSideCard field, but found none " + msg + "]");
|
||||
}
|
||||
} else if (class1 == AbilityImpl.class) {
|
||||
if (!hasWatchersField) {
|
||||
fail(originalCard, "copy", "was expecting a watchers field, but found none " + msg + "]");
|
||||
}
|
||||
if (!hasModesField) {
|
||||
fail(originalCard, "copy", "was expecting a modes field, but found none " + msg + "]");
|
||||
}
|
||||
}
|
||||
|
||||
class1 = class1.getSuperclass();
|
||||
class2 = class2.getSuperclass();
|
||||
} while (class1 != Object.class && class1 != null);
|
||||
} else if (obj1 instanceof Collection) {
|
||||
Collection col1 = (Collection) obj1;
|
||||
Collection col2 = (Collection) obj2;
|
||||
Iterator it1 = col1.iterator();
|
||||
Iterator it2 = col2.iterator();
|
||||
int i = 0;
|
||||
while (it1.hasNext() && it2.hasNext()) {
|
||||
compareClassRecursive(it1.next(), it2.next(), originalCard, msg + "<" + obj1.getClass() + ">" + "[" + i++ + "]", maxDepth - 1, alreadyChecked, useRecursive);
|
||||
}
|
||||
if (it1.hasNext() || it2.hasNext()) {
|
||||
fail(originalCard, "copy", "not same size for " + msg + "]");
|
||||
}
|
||||
} else if (obj1 instanceof Map) {
|
||||
Map map1 = (Map) obj1;
|
||||
Map map2 = (Map) obj2;
|
||||
map1.forEach((i, el1) -> {
|
||||
compareClassRecursive(el1, ((Map<?, ?>) obj2).get(i), originalCard, msg + "<" + obj1.getClass() + ">" + ".(" + i + ")", maxDepth - 1, alreadyChecked, useRecursive);
|
||||
});
|
||||
if (map1.size() != map2.size()) {
|
||||
fail(originalCard, "copy", "not same size for " + msg + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue