diff --git a/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java b/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java index 5e3898108c0..a387e1124db 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java +++ b/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java @@ -17,7 +17,7 @@ public class AmountTestableResult extends BaseTestableResult { } @Override - public Boolean getResAssert() { + public String getResAssert() { return null; // TODO: implement } diff --git a/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java index f9d67d107dc..2b605187fe4 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java +++ b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java @@ -15,6 +15,7 @@ import mage.target.common.TargetPermanentOrPlayer; */ abstract class BaseTestableDialog implements TestableDialog { + private Integer regNumber; // dialog number in runner (use it to find results and debugging) private final String group; private final String name; private final String description; @@ -27,6 +28,16 @@ abstract class BaseTestableDialog implements TestableDialog { this.result = result; } + @Override + public void setRegNumber(Integer regNumber) { + this.regNumber = regNumber; + } + + @Override + public Integer getRegNumber() { + return this.regNumber; + } + @Override final public String getGroup() { return this.group; diff --git a/Mage.Common/src/main/java/mage/utils/testers/BaseTestableResult.java b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableResult.java index 7c7e0cdf72b..5b6185ff5f6 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/BaseTestableResult.java +++ b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableResult.java @@ -25,7 +25,7 @@ public class BaseTestableResult implements TestableResult { } @Override - public Boolean getResAssert() { + public String getResAssert() { return null; // TODO: implement } diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChoiceTestableResult.java b/Mage.Common/src/main/java/mage/utils/testers/ChoiceTestableResult.java index e6578964847..eeaa7559bf5 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/ChoiceTestableResult.java +++ b/Mage.Common/src/main/java/mage/utils/testers/ChoiceTestableResult.java @@ -17,7 +17,7 @@ public class ChoiceTestableResult extends BaseTestableResult { } @Override - public Boolean getResAssert() { + public String getResAssert() { return null; // TODO: implement } diff --git a/Mage.Common/src/main/java/mage/utils/testers/MultiAmountTestableResult.java b/Mage.Common/src/main/java/mage/utils/testers/MultiAmountTestableResult.java index a3e6ba7a6db..45a80f842bf 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/MultiAmountTestableResult.java +++ b/Mage.Common/src/main/java/mage/utils/testers/MultiAmountTestableResult.java @@ -18,7 +18,7 @@ public class MultiAmountTestableResult extends BaseTestableResult { } @Override - public Boolean getResAssert() { + public String getResAssert() { return null; // TODO: implement } diff --git a/Mage.Common/src/main/java/mage/utils/testers/TargetTestableResult.java b/Mage.Common/src/main/java/mage/utils/testers/TargetTestableResult.java index 74ab97f4221..62e33a1d627 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/TargetTestableResult.java +++ b/Mage.Common/src/main/java/mage/utils/testers/TargetTestableResult.java @@ -23,7 +23,7 @@ public class TargetTestableResult extends BaseTestableResult { } @Override - public Boolean getResAssert() { + public String getResAssert() { if (!this.aiAssertEnabled) { return null; } @@ -35,16 +35,22 @@ public class TargetTestableResult extends BaseTestableResult { // wrong choose if (this.getResStatus() != this.aiAssertResStatus) { - return false; + return String.format("Wrong status: need %s, but get %s", + this.aiAssertResStatus, + this.getResStatus() + ); } // wrong targets if (this.target.getTargets().size() != this.aiAssertTargetsCount) { - return false; + return String.format("Wrong targets count: need %d, but get %d", + this.aiAssertTargetsCount, + this.target.getTargets().size() + ); } // all fine - return true; + return ""; } @Override diff --git a/Mage.Common/src/main/java/mage/utils/testers/TestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/TestableDialog.java index 533eb1fc33e..d5e86740ff8 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/TestableDialog.java +++ b/Mage.Common/src/main/java/mage/utils/testers/TestableDialog.java @@ -17,6 +17,10 @@ import mage.players.Player; */ public interface TestableDialog { + void setRegNumber(Integer regNumber); + + Integer getRegNumber(); + String getGroup(); String getName(); diff --git a/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java b/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java index 3e2bc463eb7..0970d8ecc94 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java +++ b/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java @@ -8,8 +8,10 @@ import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; -import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -55,7 +57,7 @@ import java.util.stream.Collectors; */ public class TestableDialogsRunner { - private final List dialogs = new ArrayList<>(); + private final Map dialogs = new LinkedHashMap<>(); static final int LAST_SELECTED_GROUP_ID = 997; static final int LAST_SELECTED_DIALOG_ID = 998; @@ -79,12 +81,14 @@ public class TestableDialogsRunner { } void registerDialog(TestableDialog dialog) { - this.dialogs.add(dialog); + Integer regNumber = this.dialogs.size() + 1; + dialog.setRegNumber(regNumber); + this.dialogs.put(regNumber, dialog); } public void selectAndShowTestableDialog(Player player, Ability source, Game game, Player opponent) { // select group or fast links - List groups = this.dialogs.stream() + List groups = this.dialogs.values().stream() .map(TestableDialog::getGroup) .distinct() .sorted() @@ -201,8 +205,8 @@ public class TestableDialogsRunner { return choice; } - public List getDialogs() { - return this.dialogs; + public Collection getDialogs() { + return this.dialogs.values(); } } diff --git a/Mage.Common/src/main/java/mage/utils/testers/TestableResult.java b/Mage.Common/src/main/java/mage/utils/testers/TestableResult.java index 2c9cec528e5..1b1d473b2c8 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/TestableResult.java +++ b/Mage.Common/src/main/java/mage/utils/testers/TestableResult.java @@ -21,7 +21,6 @@ public interface TestableResult { /** * Save new result after show dialog - * */ void onFinish(boolean resStatus, List resDetails); @@ -29,5 +28,11 @@ public interface TestableResult { void onClear(); - Boolean getResAssert(); + /** + * Assert dialog result + * - null - not ready (dev must setup wanted result) + * - empty - good + * - not empty - fail (return error message) + */ + String getResAssert(); } diff --git a/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java b/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java index bdc05abc352..763f32ce667 100644 --- a/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java @@ -14,8 +14,9 @@ import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; import java.util.Collections; +import java.util.HashMap; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Map; import java.util.stream.Collectors; /** @@ -36,8 +37,7 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { addCard(Zone.HAND, playerA, "Forest", 6); runCode("run single", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { - TestableDialog dialog = findDialogs(runner, "target.choose(you, target)", "any 0-3").get(0); - Assert.assertNotNull(dialog); + TestableDialog dialog = findDialog(runner, "target.choose(you, target)", "any 0-3"); dialog.prepare(); dialog.showDialog(playerA, fakeAbility, game, playerB); }); @@ -51,7 +51,8 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { setStopAt(1, PhaseStep.END_TURN); execute(); - printRunnerResults(false, false); + // it's ok to have wrong targets message cause manual testing selected x2, not AI's x3 + assertAndPrintRunnerResults(false, false); } @Test @@ -62,8 +63,7 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerA); aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerB); runCode("run single", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { - TestableDialog dialog = findDialogs(runner, "target.choose(you, target)", "any 0-3").get(0); - Assert.assertNotNull(dialog); + TestableDialog dialog = findDialog(runner, "target.choose(you, target)", "any 0-3"); dialog.prepare(); dialog.showDialog(playerA, fakeAbility, game, playerB); }); @@ -72,7 +72,30 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { setStopAt(1, PhaseStep.END_TURN); execute(); - printRunnerResults(false, true); + assertAndPrintRunnerResults(false, true); + } + + @Test + @Ignore // debug only - run single dialog by reg number + public void test_RunSingle_Debugging() { + int needRedNumber = 95; + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + addCard(Zone.HAND, playerA, "Forest", 6); + + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerA); + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerB); + runCode("run by number", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { + TestableDialog dialog = findDialog(runner, needRedNumber); + dialog.prepare(); + dialog.showDialog(playerA, fakeAbility, game, playerB); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAndPrintRunnerResults(false, true); } @Test @@ -86,17 +109,16 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerA); aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerB); - AtomicInteger dialogNumber = new AtomicInteger(); runCode("run all", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { runner.getDialogs().forEach(dialog -> { - dialogNumber.incrementAndGet(); System.out.println(String.format("run testable dialog %d of %d (%s, %s - %s)", - dialogNumber.get(), + dialog.getRegNumber(), runner.getDialogs().size(), dialog.getClass().getSimpleName(), dialog.getGroup(), dialog.getName() )); + dialog.prepare(); dialog.showDialog(playerA, fakeAbility, game, playerB); }); }); @@ -105,20 +127,31 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { setStopAt(1, PhaseStep.END_TURN); execute(); - printRunnerResults(true, true); + assertAndPrintRunnerResults(true, true); } - private List findDialogs(TestableDialogsRunner runner, String byGroup, String byName) { - return runner.getDialogs().stream() - .filter(d -> d.getGroup().equals(byGroup)) - .filter(d -> d.getName().equals(byName)) + private TestableDialog findDialog(TestableDialogsRunner runner, String byGroup, String byName) { + List res = runner.getDialogs().stream() + .filter(dialog -> dialog.getGroup().equals(byGroup)) + .filter(dialog -> dialog.getName().equals(byName)) .collect(Collectors.toList()); + Assert.assertEquals("must found only 1 dialog", 1, res.size()); + return res.get(0); } - private void printRunnerResults(boolean showFullList, boolean assertGoodResults) { + private TestableDialog findDialog(TestableDialogsRunner runner, Integer byRegNumber) { + List res = runner.getDialogs().stream() + .filter(dialog -> dialog.getRegNumber().equals(byRegNumber)) + .collect(Collectors.toList()); + Assert.assertEquals("must found only 1 dialog", 1, res.size()); + return res.get(0); + } + + private void assertAndPrintRunnerResults(boolean showFullList, boolean failOnBadResults) { // print text table with full dialogs list and results // found table sizes + int maxNumberLength = "9999".length(); int maxGroupLength = "Group".length(); int maxNameLength = "Name".length(); for (TestableDialog dialog : runner.getDialogs()) { @@ -130,15 +163,18 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { } int maxResultLength = "Result".length(); - String rowFormat = "| %-" + maxGroupLength + "s | %-" + maxNameLength + "s | %-" + maxResultLength + "s |%n"; + String rowFormat = "| %-" + maxNumberLength + "s | %-" + maxGroupLength + "s | %-" + maxNameLength + "s | %-" + maxResultLength + "s |%n"; String horizontalBorder = "+-" + + String.join("", Collections.nCopies(maxNumberLength, "-")) + "-+-" + String.join("", Collections.nCopies(maxGroupLength, "-")) + "-+-" + String.join("", Collections.nCopies(maxNameLength, "-")) + "-+-" + String.join("", Collections.nCopies(maxResultLength, "-")) + "-+"; + String totalsLeftFormat = "| %-" + (maxNumberLength + maxGroupLength + maxNameLength + maxResultLength + 9) + "s |%n"; + String totalsRightFormat = "| %" + (maxNumberLength + maxGroupLength + maxNameLength + maxResultLength + 9) + "s |%n"; // header System.out.println(horizontalBorder); - System.out.printf(rowFormat, "Group", "Name", "Result"); + System.out.printf(rowFormat, "N", "Group", "Name", "Result"); System.out.println(horizontalBorder); // data @@ -147,47 +183,94 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { int totalGood = 0; int totalBad = 0; int totalUnknown = 0; + boolean usedHorizontalBorder = true; // mark that last print used horizontal border (fix duplicates) + Map coloredTexts = new HashMap<>(); // must colorize after string format to keep pretty table for (TestableDialog dialog : runner.getDialogs()) { if (!showFullList && !dialog.getResult().isFinished()) { + // print only required dialogs continue; } totalDialogs++; if (!prevGroup.isEmpty() && !prevGroup.equals(dialog.getGroup())) { - System.out.println(horizontalBorder); + if (!usedHorizontalBorder) { + System.out.println(horizontalBorder); + usedHorizontalBorder = true; + } } prevGroup = dialog.getGroup(); + + // print dialog stats String status; - if (dialog.getResult().getResAssert() == null) { - status = asYellow("?"); + coloredTexts.clear(); + String resAssert = dialog.getResult().getResAssert(); + String assertError = ""; + if (resAssert == null) { totalUnknown++; - } else if (dialog.getResult().getResAssert()) { + status = "?"; + coloredTexts.put("?", asYellow("?")); + } else if (resAssert.isEmpty()) { totalGood++; - status = asGreen("OK"); + status = "OK"; + coloredTexts.put("OK", asGreen("OK")); } else { totalBad++; - status = asRed("FAIL"); + status = "FAIL"; + coloredTexts.put("FAIL", asRed("FAIL")); + assertError = resAssert; + } + if (!assertError.isEmpty()) { + if (!usedHorizontalBorder) { + System.out.println(horizontalBorder); + usedHorizontalBorder = true; + } + } + System.out.print(getColoredRow(rowFormat, coloredTexts, dialog.getRegNumber(), dialog.getGroup(), dialog.getName(), status)); + usedHorizontalBorder = false; + + // print dialog error + if (!assertError.isEmpty()) { + coloredTexts.clear(); + coloredTexts.put(resAssert, asRed(resAssert)); + System.out.print(getColoredRow(totalsRightFormat, coloredTexts, String.format("%s", resAssert))); + System.out.println(horizontalBorder); + usedHorizontalBorder = true; } - System.out.printf(rowFormat, dialog.getGroup(), dialog.getName(), status); } - System.out.println(horizontalBorder); + if (!usedHorizontalBorder) { + System.out.println(horizontalBorder); + usedHorizontalBorder = true; + } - // totals - System.out.printf("| %-" + (maxGroupLength + maxNameLength + maxResultLength + 6) + "s |%n", - "Total dialogs: " + totalDialogs); - System.out.printf("| %-" + (maxGroupLength + maxNameLength + maxResultLength + 6) + "s |%n", - String.format("Total results: %s good, %s bad, %s unknown", - asGreen(String.valueOf(totalGood)), - asRed(String.valueOf(totalBad)), - asYellow(String.valueOf(totalUnknown)) - ) - ); + // totals dialogs + System.out.printf(totalsLeftFormat, "Total dialogs: " + totalDialogs); + usedHorizontalBorder = false; + // totals results + String goodStats = String.format("%d good", totalGood); + String badStats = String.format("%d bad", totalBad); + String unknownStats = String.format("%d unknown", totalUnknown); + coloredTexts.clear(); + coloredTexts.put(goodStats, String.format("%s good", asGreen(String.valueOf(totalGood)))); + coloredTexts.put(badStats, String.format("%s bad", asRed(String.valueOf(totalBad)))); + coloredTexts.put(unknownStats, String.format("%s unknown", asYellow(String.valueOf(totalUnknown)))); + System.out.print(getColoredRow(totalsLeftFormat, coloredTexts, String.format("Total results: %s, %s, %s", + goodStats, badStats, unknownStats))); + // table end System.out.println(horizontalBorder); + usedHorizontalBorder = true; - if (assertGoodResults && totalBad > 0) { + if (failOnBadResults && totalBad > 0) { Assert.fail(String.format("Testable dialogs has %d bad results, try to fix it", totalBad)); } } + private String getColoredRow(String rowFormat, Map coloredTexts, Object... args) { + String line = String.format(rowFormat, args); + for (String coloredText : coloredTexts.keySet()) { + line = line.replace(coloredText, coloredTexts.get(coloredText)); + } + return line; + } + private String asRed(String text) { return "\u001B[31m" + text + "\u001B[0m"; }