Merge branch '_RHH/master' into _RHH/upcoming

# Conflicts:
#	ld_script_modern.ld
#	src/battle_ai_switch_items.c
This commit is contained in:
Eduardo Quezada 2024-02-01 12:52:31 -03:00
commit 09d12fb154
11 changed files with 139 additions and 58 deletions

View file

@ -7,11 +7,11 @@ JASC-PAL
201 201 201 201 201 201
169 169 169 169 169 169
129 129 129 129 129 129
249 153 161 106 106 106
233 49 49 37 37 37
193 33 41 106 106 106
145 17 33 0 0 0
249 153 161 106 106 106
193 33 41 193 33 41
141 251 184 141 251 184
52 66 162 52 66 162

View file

@ -337,7 +337,7 @@ struct AiLogicData
bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler. bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler.
u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch. u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch.
bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once. bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once.
u8 mostSuitableMonId; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId.
struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c
}; };

View file

@ -18,6 +18,11 @@ SECTIONS {
ALIGN(4) ALIGN(4)
{ {
__ewram_start = .; __ewram_start = .;
/*
We link malloc.o here to prevent `gHeap` from landing in the middle of EWRAM.
Otherwise this causes corruption issues on some ld versions
*/
gflib/malloc.o(ewram_data);
*(.ewram*) *(.ewram*)
__ewram_end = .; __ewram_end = .;
} > EWRAM } > EWRAM

View file

@ -5,6 +5,7 @@
#include "battle_anim.h" #include "battle_anim.h"
#include "battle_ai_util.h" #include "battle_ai_util.h"
#include "battle_ai_main.h" #include "battle_ai_main.h"
#include "battle_controllers.h"
#include "battle_factory.h" #include "battle_factory.h"
#include "battle_setup.h" #include "battle_setup.h"
#include "battle_z_move.h" #include "battle_z_move.h"
@ -455,11 +456,21 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
} }
} }
static bool32 AI_SwitchMonIfSuitable(u32 battler) static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle)
{ {
u32 monToSwitchId = AI_DATA->mostSuitableMonId; u32 monToSwitchId = AI_DATA->mostSuitableMonId[battler];
if (monToSwitchId != PARTY_SIZE) if (monToSwitchId != PARTY_SIZE && IsValidForBattle(&GetBattlerParty(battler)[monToSwitchId]))
{ {
gBattleMoveDamage = monToSwitchId;
// Edge case: See if partner already chose to switch into the same mon
if (doubleBattle)
{
u32 partner = BATTLE_PARTNER(battler);
if (AI_DATA->shouldSwitchMon & gBitTable[partner] && AI_DATA->monToSwitchId[partner] == monToSwitchId)
{
return FALSE;
}
}
AI_DATA->shouldSwitchMon |= gBitTable[battler]; AI_DATA->shouldSwitchMon |= gBitTable[battler];
AI_DATA->monToSwitchId[battler] = monToSwitchId; AI_DATA->monToSwitchId[battler] = monToSwitchId;
return TRUE; return TRUE;
@ -496,7 +507,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
break; break;
} }
} }
if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler)) if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler, doubleBattle))
return TRUE; return TRUE;
} }
else else
@ -507,7 +518,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
break; break;
} }
if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler)) if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler, doubleBattle))
return TRUE; return TRUE;
} }
@ -519,7 +530,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
&& IsTruantMonVulnerable(battler, gBattlerTarget) && IsTruantMonVulnerable(battler, gBattlerTarget)
&& gDisableStructs[battler].truantCounter && gDisableStructs[battler].truantCounter
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2
&& AI_SwitchMonIfSuitable(battler)) && AI_SwitchMonIfSuitable(battler, doubleBattle))
{ {
return TRUE; return TRUE;
} }

View file

@ -167,7 +167,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
} }
// If we don't have any other viable options, don't switch out // If we don't have any other viable options, don't switch out
if (AI_DATA->mostSuitableMonId == PARTY_SIZE) if (AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE)
return FALSE; return FALSE;
// Start assessing whether or not mon has bad odds // Start assessing whether or not mon has bad odds
@ -603,12 +603,12 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult)
moduloChance = 4; //25% moduloChance = 4; //25%
//Attempt to cure bad ailment //Attempt to cure bad ailment
if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON)
&& AI_DATA->mostSuitableMonId != PARTY_SIZE) && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
break; break;
//Attempt to cure lesser ailment //Attempt to cure lesser ailment
if ((gBattleMons[battler].status1 & STATUS1_ANY) if ((gBattleMons[battler].status1 & STATUS1_ANY)
&& (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2)
&& AI_DATA->mostSuitableMonId != PARTY_SIZE && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE
&& Random() % (moduloChance*chanceReducer) == 0) && Random() % (moduloChance*chanceReducer) == 0)
break; break;
@ -620,7 +620,7 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult)
if (gBattleMons[battler].status1 & STATUS1_ANY) if (gBattleMons[battler].status1 & STATUS1_ANY)
return FALSE; return FALSE;
if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3)) if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3))
&& AI_DATA->mostSuitableMonId != PARTY_SIZE && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE
&& Random() % (moduloChance*chanceReducer) == 0) && Random() % (moduloChance*chanceReducer) == 0)
break; break;
@ -856,7 +856,7 @@ static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult)
return FALSE; return FALSE;
// If not Encored or if no good switchin, don't switch // If not Encored or if no good switchin, don't switch
if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId == PARTY_SIZE) if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE)
return FALSE; return FALSE;
// Otherwise 50% chance to switch out // Otherwise 50% chance to switch out
@ -890,7 +890,7 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
// 50% chance if attack at -2 and have a good candidate mon // 50% chance if attack at -2 and have a good candidate mon
else if (attackingStage == DEFAULT_STAT_STAGE - 2) else if (attackingStage == DEFAULT_STAT_STAGE - 2)
{ {
if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
{ {
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
@ -915,7 +915,7 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
// 50% chance if attack at -2 and have a good candidate mon // 50% chance if attack at -2 and have a good candidate mon
else if (spAttackingStage == DEFAULT_STAT_STAGE - 2) else if (spAttackingStage == DEFAULT_STAT_STAGE - 2)
{ {
if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
{ {
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
@ -1064,7 +1064,7 @@ void AI_TrySwitchOrUseItem(u32 battler)
{ {
if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE) if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE)
{ {
s32 monToSwitchId = AI_DATA->mostSuitableMonId; s32 monToSwitchId = AI_DATA->mostSuitableMonId[battler];
if (monToSwitchId == PARTY_SIZE) if (monToSwitchId == PARTY_SIZE)
{ {
if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE))

View file

@ -4075,7 +4075,7 @@ static void HandleTurnActionSelectionState(void)
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart())
&& (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE))) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE)))
{ {
AI_DATA->mostSuitableMonId = GetMostSuitableMonToSwitchInto(battler, FALSE); AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, FALSE);
gBattleStruct->aiMoveOrAction[battler] = ComputeBattleAiScores(battler); gBattleStruct->aiMoveOrAction[battler] = ComputeBattleAiScores(battler);
} }
// fallthrough // fallthrough

View file

@ -6550,7 +6550,7 @@ static u8 ItemHealHp(u32 battler, u32 itemId, bool32 end2, bool32 percentHeal)
gBattlescriptCurrInstr = BattleScript_ItemHealHP_RemoveItemRet; gBattlescriptCurrInstr = BattleScript_ItemHealHP_RemoveItemRet;
} }
if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_EMERGENCY_EXIT if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_EMERGENCY_EXIT
&& GetNonDynamaxMaxHP(battler) > gBattleMons[battler].maxHP / 2) && GetNonDynamaxHP(battler) >= GetNonDynamaxMaxHP(battler) / 2)
gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_EMERGENCY_EXIT; gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_EMERGENCY_EXIT;
return ITEM_HP_CHANGE; return ITEM_HP_CHANGE;

View file

@ -8,10 +8,7 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage")
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); };
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
TURN { TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); }
MOVE(player, MOVE_SUPER_FANG);
SEND_OUT(opponent, 1);
}
} SCENE { } SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player);
HP_BAR(opponent); HP_BAR(opponent);
@ -19,16 +16,14 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage")
} }
} }
SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage after a restore hp hold effect was used") SINGLE_BATTLE_TEST("Emergency Exit does not switch out when going below 50% max-HP but healed via held item back above the threshold")
{ {
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET) PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); Item(ITEM_SITRUS_BERRY); }; OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); Item(ITEM_SITRUS_BERRY); };
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
TURN { TURN { MOVE(player, MOVE_SUPER_FANG); }
MOVE(player, MOVE_SUPER_FANG);
}
} SCENE { } SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player);
HP_BAR(opponent); HP_BAR(opponent);
@ -36,3 +31,19 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage af
NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
} }
} }
SINGLE_BATTLE_TEST("Emergency Exit switches out when going below 50% max-HP but healing via held item is not enough to go back above the threshold")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); Item(ITEM_ORAN_BERRY); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}

View file

@ -702,3 +702,31 @@ AI_SINGLE_BATTLE_TEST("First Impression is not chosen if it's blocked by certain
TURN { EXPECT_MOVE(opponent, MOVE_LUNGE); } TURN { EXPECT_MOVE(opponent, MOVE_LUNGE); }
} }
} }
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle")
{
u32 flags;
PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; }
PARAMETRIZE {flags = 0; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags);
PLAYER(SPECIES_RATTATA);
PLAYER(SPECIES_RATTATA);
// No moves to damage player.
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
OPPONENT(SPECIES_HAUNTER) { Moves(MOVE_SHADOW_BALL); }
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
OPPONENT(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); }
} WHEN {
TURN { EXPECT_SWITCH(opponentLeft, 3); };
} SCENE {
MESSAGE("{PKMN} TRAINER LEAF withdrew Gengar!");
MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!");
NONE_OF {
MESSAGE("{PKMN} TRAINER LEAF withdrew Haunter!");
MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!");
}
}
}

View file

@ -30,7 +30,6 @@ void TestRunner_Battle(const struct Test *);
static bool32 MgbaOpen_(void); static bool32 MgbaOpen_(void);
static void MgbaExit_(u8 exitCode); static void MgbaExit_(u8 exitCode);
static s32 MgbaPuts_(const char *s);
static s32 MgbaVPrintf_(const char *fmt, va_list va); static s32 MgbaVPrintf_(const char *fmt, va_list va);
static void Intr_Timer2(void); static void Intr_Timer2(void);
@ -293,12 +292,6 @@ top:
color = ""; color = "";
} }
if (gTestRunnerState.result == TEST_RESULT_PASS
&& gTestRunnerState.result != gTestRunnerState.expectedResult)
{
MgbaPuts_("\e[31mPlease remove KNOWN_FAILING if this test intentionally PASSes\e[0m");
}
switch (gTestRunnerState.result) switch (gTestRunnerState.result)
{ {
case TEST_RESULT_FAIL: case TEST_RESULT_FAIL:
@ -313,7 +306,10 @@ top:
} }
break; break;
case TEST_RESULT_PASS: case TEST_RESULT_PASS:
result = "PASS"; if (gTestRunnerState.result != gTestRunnerState.expectedResult)
result = "KNOWN_FAILING_PASS";
else
result = "PASS";
break; break;
case TEST_RESULT_ASSUMPTION_FAIL: case TEST_RESULT_ASSUMPTION_FAIL:
result = "ASSUMPTION_FAIL"; result = "ASSUMPTION_FAIL";
@ -341,7 +337,12 @@ top:
} }
if (gTestRunnerState.result == TEST_RESULT_PASS) if (gTestRunnerState.result == TEST_RESULT_PASS)
MgbaPrintf_(":P%s%s\e[0m", color, result); {
if (gTestRunnerState.result != gTestRunnerState.expectedResult)
MgbaPrintf_(":U%s%s\e[0m", color, result);
else
MgbaPrintf_(":P%s%s\e[0m", color, result);
}
else if (gTestRunnerState.result == TEST_RESULT_ASSUMPTION_FAIL) else if (gTestRunnerState.result == TEST_RESULT_ASSUMPTION_FAIL)
MgbaPrintf_(":A%s%s\e[0m", color, result); MgbaPrintf_(":A%s%s\e[0m", color, result);
else if (gTestRunnerState.result == TEST_RESULT_TODO) else if (gTestRunnerState.result == TEST_RESULT_TODO)
@ -513,11 +514,6 @@ static void MgbaExit_(u8 exitCode)
asm("swi 0x3" :: "r" (_exitCode)); asm("swi 0x3" :: "r" (_exitCode));
} }
static s32 MgbaPuts_(const char *s)
{
return MgbaPrintf_("%s", s);
}
s32 MgbaPrintf_(const char *fmt, ...) s32 MgbaPrintf_(const char *fmt, ...)
{ {
va_list va; va_list va;

View file

@ -35,7 +35,7 @@
#define min(a, b) ((a) < (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b))
#define MAX_PROCESSES 32 // See also test/test.h #define MAX_PROCESSES 32 // See also test/test.h
#define MAX_FAILED_TESTS_TO_LIST 100 #define MAX_SUMMARY_TESTS_TO_LIST 50
#define MAX_TEST_LIST_BUFFER_LENGTH 256 #define MAX_TEST_LIST_BUFFER_LENGTH 256
#define ARRAY_COUNT(arr) (sizeof((arr)) / sizeof((arr)[0])) #define ARRAY_COUNT(arr) (sizeof((arr)) / sizeof((arr)[0]))
@ -54,11 +54,13 @@ struct Runner
char *output_buffer; char *output_buffer;
int passes; int passes;
int knownFails; int knownFails;
int knownFailsPassing;
int todos; int todos;
int assumptionFails; int assumptionFails;
int fails; int fails;
int results; int results;
char failedTestNames[MAX_FAILED_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH];
char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH];
}; };
static unsigned nrunners = 0; static unsigned nrunners = 0;
@ -107,6 +109,11 @@ static void handle_read(int i, struct Runner *runner)
case 'K': case 'K':
runner->knownFails++; runner->knownFails++;
goto add_to_results; goto add_to_results;
case 'U':
if (runner->knownFailsPassing < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(runner->knownFailingPassedTestNames[runner->knownFailsPassing], runner->test_name);
runner->knownFailsPassing++;
goto add_to_results;
case 'T': case 'T':
runner->todos++; runner->todos++;
goto add_to_results; goto add_to_results;
@ -114,7 +121,7 @@ static void handle_read(int i, struct Runner *runner)
runner->assumptionFails++; runner->assumptionFails++;
goto add_to_results; goto add_to_results;
case 'F': case 'F':
if (runner->fails < MAX_FAILED_TESTS_TO_LIST) if (runner->fails < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(runner->failedTestNames[runner->fails], runner->test_name); strcpy(runner->failedTestNames[runner->fails], runner->test_name);
runner->fails++; runner->fails++;
add_to_results: add_to_results:
@ -519,12 +526,14 @@ int main(int argc, char *argv[])
int exit_code = 0; int exit_code = 0;
int passes = 0; int passes = 0;
int knownFails = 0; int knownFails = 0;
int knownFailsPassing = 0;
int todos = 0; int todos = 0;
int assumptionFails = 0; int assumptionFails = 0;
int fails = 0; int fails = 0;
int results = 0; int results = 0;
char failedTestNames[MAX_FAILED_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH];
char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH];
for (int i = 0; i < nrunners; i++) for (int i = 0; i < nrunners; i++)
{ {
@ -540,18 +549,25 @@ int main(int argc, char *argv[])
exit_code = WEXITSTATUS(wstatus); exit_code = WEXITSTATUS(wstatus);
passes += runners[i].passes; passes += runners[i].passes;
knownFails += runners[i].knownFails; knownFails += runners[i].knownFails;
for (int j = 0; j < runners[i].knownFailsPassing; j++)
{
if (j < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(knownFailingPassedTestNames[fails], runners[i].knownFailingPassedTestNames[j]);
knownFailsPassing++;
}
todos += runners[i].todos; todos += runners[i].todos;
assumptionFails += runners[i].assumptionFails; assumptionFails += runners[i].assumptionFails;
for (int j = 0; j < runners[i].fails; j++) for (int j = 0; j < runners[i].fails; j++)
{ {
if (j < MAX_FAILED_TESTS_TO_LIST) if (j < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(failedTestNames[fails], runners[i].failedTestNames[j]); strcpy(failedTestNames[fails], runners[i].failedTestNames[j]);
fails++; fails++;
} }
results += runners[i].results; results += runners[i].results;
} }
qsort(failedTestNames, min(fails, MAX_FAILED_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); qsort(failedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings);
qsort(knownFailingPassedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings);
if (results == 0) if (results == 0)
{ {
@ -559,28 +575,42 @@ int main(int argc, char *argv[])
} }
else else
{ {
fprintf(stdout, "\n");
if (fails > 0) if (fails > 0)
{ {
fprintf(stdout, "\n- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails); fprintf(stdout, "- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails);
for (int i = 0; i < fails; i++) for (int i = 0; i < fails; i++)
{ {
if (i >= MAX_FAILED_TESTS_TO_LIST) if (i >= MAX_SUMMARY_TESTS_TO_LIST)
{ {
fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_FAILED_TESTS_TO_LIST); fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_SUMMARY_TESTS_TO_LIST);
break; break;
} }
fprintf(stdout, " - \e[31m%s\e[0m.\n", failedTestNames[i]); fprintf(stdout, " - \e[31m%s\e[0m.\n", failedTestNames[i]);
} }
} }
fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes); if (knownFailsPassing > 0)
{
fprintf(stdout, "- \e[31mKNOWN_FAILING_PASSED\e[0m: %d \e[31mPlease remove KNOWN_FAILING if these tests intentionally PASS\e[0m\n", knownFailsPassing);
for (int i = 0; i < knownFailsPassing; i++)
{
if (i >= MAX_SUMMARY_TESTS_TO_LIST)
{
fprintf(stdout, " - \e[31mand %d more...\e[0m\n", knownFailsPassing - MAX_SUMMARY_TESTS_TO_LIST);
break;
}
fprintf(stdout, " - \e[31m%s\e[0m.\n", knownFailingPassedTestNames[i]);
}
}
fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes);
if (knownFails > 0) if (knownFails > 0)
fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails); fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails);
if (todos > 0) if (todos > 0)
fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos); fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos);
if (assumptionFails > 0) if (assumptionFails > 0)
fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails); fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails);
fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results); fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results);
} }
fprintf(stdout, "\n"); fprintf(stdout, "\n");