mirror of
https://github.com/magefree/mage.git
synced 2026-01-10 04:42:07 -08:00
tests: added verify check for wrong predicates usage in filters (ClassCastException errors like #12774)
This commit is contained in:
parent
e1f76c2b6c
commit
cd51954208
14 changed files with 176 additions and 91 deletions
|
|
@ -73,7 +73,9 @@ public class FilterCard extends FilterObject<Card> {
|
|||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
checkPredicateIsSuitableForCardFilter(predicate);
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, Card.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import mage.constants.SubType;
|
|||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
|
|
@ -12,7 +13,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author North
|
||||
|
|
@ -64,6 +64,10 @@ public class FilterPermanent extends FilterObject<Permanent> implements FilterIn
|
|||
if (isLockedFilter()) {
|
||||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import mage.abilities.Ability;
|
|||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
|
|
@ -36,6 +37,10 @@ public class FilterPlayer extends FilterImpl<Player> {
|
|||
if (isLockedFilter()) {
|
||||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, Player.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package mage.filter;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -43,6 +46,11 @@ public class FilterStackObject extends FilterObject<StackObject> {
|
|||
if (isLockedFilter()) {
|
||||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
// Spell implements Card interface, so it can use some default predicates like owner
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, StackObject.class, Spell.class, Card.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.filter.predicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package mage.filter.predicate;
|
|||
|
||||
import mage.game.Game;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
|
@ -11,7 +13,7 @@ import java.util.List;
|
|||
*
|
||||
* <p>All methods returns serializable predicates as long as they're given serializable parameters.</p>
|
||||
*
|
||||
* @author North
|
||||
* @author North, JayDi85
|
||||
*/
|
||||
public final class Predicates {
|
||||
|
||||
|
|
@ -246,4 +248,48 @@ public final class Predicates {
|
|||
extraPredicates.forEach(p -> collectAllComponents(p, res));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify check: try to find filters usage
|
||||
* Example use case: Player predicate was used for Permanent filter
|
||||
* Example error: java.lang.ClassCastException: mage.game.permanent.PermanentToken cannot be cast to mage.players.Player
|
||||
*/
|
||||
public static void makeSurePredicateCompatibleWithFilter(Predicate predicate, Class... compatibleClasses) {
|
||||
List<Predicate> list = new ArrayList<>();
|
||||
Predicates.collectAllComponents(predicate, list);
|
||||
list.forEach(p -> {
|
||||
Class predicateGenericParamClass = findGenericParam(predicate);
|
||||
if (predicateGenericParamClass == null) {
|
||||
throw new IllegalArgumentException("Somthing wrong. Can't find predicate's generic param for " + predicate.getClass());
|
||||
}
|
||||
if (Arrays.stream(compatibleClasses).anyMatch(f -> predicateGenericParamClass.isAssignableFrom(f))) {
|
||||
// predicate is fine
|
||||
} else {
|
||||
// How-to fix: use correct predicates (same type, e.g. getControllerPredicate() instead getPlayerPredicate())
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Wrong code usage: predicate [%s] with generic param [%s] can't be added to filter, allow only %s",
|
||||
predicate.getClass(),
|
||||
predicateGenericParamClass,
|
||||
Arrays.toString(compatibleClasses)
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Class findGenericParam(Predicate predicate) {
|
||||
Type[] interfaces = predicate.getClass().getGenericInterfaces();
|
||||
for (Type type : interfaces) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
|
||||
if (actualTypeArguments.length > 0) {
|
||||
Type actualType = actualTypeArguments[0];
|
||||
if (actualType instanceof Class) {
|
||||
return (Class) actualType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
63
Mage/src/main/java/mage/game/FakeGame.java
Normal file
63
Mage/src/main/java/mage/game/FakeGame.java
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package mage.game;
|
||||
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.match.MatchType;
|
||||
import mage.game.mulligan.MulliganType;
|
||||
|
||||
/**
|
||||
* Fake game for tests and data check, do nothing.
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class FakeGame extends GameImpl {
|
||||
|
||||
private int numPlayers;
|
||||
|
||||
public FakeGame() {
|
||||
super(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 60, 20, 7);
|
||||
}
|
||||
|
||||
public FakeGame(final FakeGame game) {
|
||||
super(game);
|
||||
this.numPlayers = game.numPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchType getGameType() {
|
||||
return new FakeGameType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumPlayers() {
|
||||
return numPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FakeGame copy() {
|
||||
return new FakeGame(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FakeGameType extends MatchType {
|
||||
|
||||
public FakeGameType() {
|
||||
this.name = "Test Game Type";
|
||||
this.maxPlayers = 10;
|
||||
this.minPlayers = 3;
|
||||
this.numTeams = 0;
|
||||
this.useAttackOption = true;
|
||||
this.useRange = true;
|
||||
this.sideboardingAllowed = true;
|
||||
}
|
||||
|
||||
protected FakeGameType(final FakeGameType matchType) {
|
||||
super(matchType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FakeGameType copy() {
|
||||
return new FakeGameType(this);
|
||||
}
|
||||
}
|
||||
21
Mage/src/main/java/mage/game/FakeMatch.java
Normal file
21
Mage/src/main/java/mage/game/FakeMatch.java
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package mage.game;
|
||||
|
||||
import mage.game.match.MatchImpl;
|
||||
import mage.game.match.MatchOptions;
|
||||
|
||||
/**
|
||||
* Fake match for tests and data check, do nothing.
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class FakeMatch extends MatchImpl {
|
||||
|
||||
public FakeMatch() {
|
||||
super(new MatchOptions("fake match", "fake game type", true, 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startGame() throws GameException {
|
||||
throw new IllegalStateException("Can't start fake match");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue