From 8ace31bde4a0eea735b683c7b2662ebfa06f9ef7 Mon Sep 17 00:00:00 2001 From: Mothblocks <35135081+Mothblocks@users.noreply.github.com> Date: Wed, 7 Dec 2022 02:58:33 -0800 Subject: [PATCH] Allows for offloaded tests to be focused (#71719) Focusing a test through TEST_FOCUS allows you to only run that one test. It's very useful for developing. However, we have a bunch of offloaded tests in `#ifdef UNIT_TESTS` that would not be focused. This changes it so that those tests will now only run if either no test is focused, or if their specific "focus only" test is focused. This is done through a `PERFORM_ALL_TESTS` macro that replaces the `#ifdef UNIT_TESTS`. This is completely free because `if (FALSE)` constant folds. Hide whitespace mode recommended. --- code/__DEFINES/_helpers.dm | 7 ------ code/__DEFINES/unit_tests.dm | 20 ++++++++++++++++ code/controllers/subsystem/overlays.dm | 14 +++++------ code/game/atoms.dm | 10 ++++---- code/game/turfs/turf.dm | 10 ++++---- .../asset_cache/assets/research_designs.dm | 18 +++++++-------- code/modules/asset_cache/assets/vending.dm | 23 +++++++++---------- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/focus_only_tests.dm | 19 +++++++++++++++ code/modules/unit_tests/unit_test.dm | 13 +++++++++++ tgstation.dme | 1 + 11 files changed, 88 insertions(+), 48 deletions(-) create mode 100644 code/__DEFINES/unit_tests.dm create mode 100644 code/modules/unit_tests/focus_only_tests.dm diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm index 89ebac7ba1b..bf723659588 100644 --- a/code/__DEFINES/_helpers.dm +++ b/code/__DEFINES/_helpers.dm @@ -24,10 +24,3 @@ /// : because of the embedded typecheck #define text_ref(datum) (isdatum(datum) ? (datum:cached_ref ||= "\ref[datum]") : ("\ref[datum]")) -/// ASSERT(), but it only actually does anything during unit tests -#ifdef UNIT_TESTS -#define TEST_ONLY_ASSERT(test, explanation) if(!(test)) {CRASH(explanation)} -#else -#define TEST_ONLY_ASSERT(test, explanation) -#endif - diff --git a/code/__DEFINES/unit_tests.dm b/code/__DEFINES/unit_tests.dm new file mode 100644 index 00000000000..92aee0ee8f4 --- /dev/null +++ b/code/__DEFINES/unit_tests.dm @@ -0,0 +1,20 @@ +/// Are tests enabled with no focus? +/// Use this when performing test assertions outside of a unit test, +/// since a focused test means that you're trying to run a test quickly. +/// If a parameter is provided, will check if the focus is on that test name. +/// For example, PERFORM_ALL_TESTS(log_mapping) will only run if either +/// no test is focused, or the focus is log_mapping. +#ifdef UNIT_TESTS +// Bit of a trick here, if focus isn't passed in then it'll check for /datum/unit_test/, which is never the case. +#define PERFORM_ALL_TESTS(focus...) (isnull(GLOB.focused_test) || GLOB.focused_test == /datum/unit_test/##focus) +#else +// UNLINT necessary here so that if (PERFORM_ALL_TESTS()) works +#define PERFORM_ALL_TESTS(...) UNLINT(FALSE) +#endif + +/// ASSERT(), but it only actually does anything during unit tests +#ifdef UNIT_TESTS +#define TEST_ONLY_ASSERT(test, explanation) if(!(test)) {CRASH(explanation)} +#else +#define TEST_ONLY_ASSERT(test, explanation) +#endif diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm index 5021ffa3ece..1f60a9e71e0 100644 --- a/code/controllers/subsystem/overlays.dm +++ b/code/controllers/subsystem/overlays.dm @@ -45,14 +45,14 @@ SUBSYSTEM_DEF(overlays) build_overlays -= overlay continue if (istext(overlay)) -#ifdef UNIT_TESTS // This is too expensive to run normally but running it during CI is a good test - var/list/icon_states_available = icon_states(icon) - if(!(overlay in icon_states_available)) - var/icon_file = "[icon]" || "Unknown Generated Icon" - stack_trace("Invalid overlay: Icon object '[icon_file]' [REF(icon)] used in '[src]' [type] is missing icon state [overlay].") - continue -#endif + if (PERFORM_ALL_TESTS(focus_only/invalid_overlays)) + var/list/icon_states_available = icon_states(icon) + if(!(overlay in icon_states_available)) + var/icon_file = "[icon]" || "Unknown Generated Icon" + stack_trace("Invalid overlay: Icon object '[icon_file]' [REF(icon)] used in '[src]' [type] is missing icon state [overlay].") + continue + var/index = build_overlays.Find(overlay) build_overlays[index] = iconstate2appearance(icon, overlay) else if(isicon(overlay)) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 9f18ac73d86..f2bfa4cbc44 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -259,16 +259,14 @@ update_light() if (length(smoothing_groups)) - #ifdef UNIT_TESTS - assert_sorted(smoothing_groups, "[type].smoothing_groups") - #endif + if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) + assert_sorted(smoothing_groups, "[type].smoothing_groups") SET_BITFLAG_LIST(smoothing_groups) if (length(canSmoothWith)) - #ifdef UNIT_TESTS - assert_sorted(canSmoothWith, "[type].canSmoothWith") - #endif + if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) + assert_sorted(canSmoothWith, "[type].canSmoothWith") if(canSmoothWith[length(canSmoothWith)] > MAX_S_TURF) //If the last element is higher than the maximum turf-only value, then it must scan turf contents for smoothing targets. smoothing_flags |= SMOOTH_OBJ diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index bdc3473cc74..1967796ea47 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -134,15 +134,13 @@ GLOBAL_LIST_EMPTY(station_turfs) levelupdate() if (length(smoothing_groups)) - #ifdef UNIT_TESTS - assert_sorted(smoothing_groups, "[type].smoothing_groups") - #endif + if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) + assert_sorted(smoothing_groups, "[type].smoothing_groups") SET_BITFLAG_LIST(smoothing_groups) if (length(canSmoothWith)) - #ifdef UNIT_TESTS - assert_sorted(canSmoothWith, "[type].canSmoothWith") - #endif + if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) + assert_sorted(canSmoothWith, "[type].canSmoothWith") if(canSmoothWith[length(canSmoothWith)] > MAX_S_TURF) //If the last element is higher than the maximum turf-only value, then it must scan turf contents for smoothing targets. smoothing_flags |= SMOOTH_OBJ diff --git a/code/modules/asset_cache/assets/research_designs.dm b/code/modules/asset_cache/assets/research_designs.dm index c457ca43c6e..62a03040517 100644 --- a/code/modules/asset_cache/assets/research_designs.dm +++ b/code/modules/asset_cache/assets/research_designs.dm @@ -13,11 +13,10 @@ if(initial(D.research_icon) && initial(D.research_icon_state)) //If the design has an icon replacement skip the rest icon_file = initial(D.research_icon) icon_state = initial(D.research_icon_state) - #ifdef UNIT_TESTS - if(!(icon_state in icon_states(icon_file))) - stack_trace("design [D] with icon '[icon_file]' missing state '[icon_state]'") - continue - #endif + if (PERFORM_ALL_TESTS(focus_only/invalid_research_designs)) + if(!(icon_state in icon_states(icon_file))) + stack_trace("design [D] with icon '[icon_file]' missing state '[icon_state]'") + continue I = icon(icon_file, icon_state, SOUTH) else @@ -46,11 +45,10 @@ icon_file = initial(item.icon) icon_state = initial(item.icon_state) - #ifdef UNIT_TESTS - if(!(icon_state in icon_states(icon_file))) - stack_trace("design [D] with icon '[icon_file]' missing state '[icon_state]'") - continue - #endif + if (PERFORM_ALL_TESTS(focus_only/invalid_research_designs)) + if(!(icon_state in icon_states(icon_file))) + stack_trace("design [D] with icon '[icon_file]' missing state '[icon_state]'") + continue I = icon(icon_file, icon_state, SOUTH) // computers (and snowflakes) get their screen and keyboard sprites diff --git a/code/modules/asset_cache/assets/vending.dm b/code/modules/asset_cache/assets/vending.dm index 574836f0c12..e8b92e59392 100644 --- a/code/modules/asset_cache/assets/vending.dm +++ b/code/modules/asset_cache/assets/vending.dm @@ -14,19 +14,18 @@ icon_file = initial(item.icon) var/icon_state = initial(item.icon_state) - #ifdef UNIT_TESTS - var/icon_states_list = icon_states(icon_file) - if (!(icon_state in icon_states_list)) - var/icon_states_string - for (var/an_icon_state in icon_states_list) - if (!icon_states_string) - icon_states_string = "[json_encode(an_icon_state)]([text_ref(an_icon_state)])" - else - icon_states_string += ", [json_encode(an_icon_state)]([text_ref(an_icon_state)])" + if (PERFORM_ALL_TESTS(focus_only/invalid_vending_machine_icon_states)) + var/icon_states_list = icon_states(icon_file) + if (!(icon_state in icon_states_list)) + var/icon_states_string + for (var/an_icon_state in icon_states_list) + if (!icon_states_string) + icon_states_string = "[json_encode(an_icon_state)]([text_ref(an_icon_state)])" + else + icon_states_string += ", [json_encode(an_icon_state)]([text_ref(an_icon_state)])" - stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)]([text_ref(icon_state)]), icon_states=[icon_states_string]") - continue - #endif + stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)]([text_ref(icon_state)]), icon_states=[icon_states_string]") + continue var/icon/I = icon(icon_file, icon_state, SOUTH) var/c = initial(item.color) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index d2f9d7029b6..f926bf6af7b 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -105,6 +105,7 @@ #include "dynamic_ruleset_sanity.dm" #include "egg_glands.dm" #include "emoting.dm" +#include "focus_only_tests.dm" #include "food_edibility_check.dm" #include "gas_transfer.dm" #include "get_turf_pixel.dm" diff --git a/code/modules/unit_tests/focus_only_tests.dm b/code/modules/unit_tests/focus_only_tests.dm new file mode 100644 index 00000000000..43f7d9ef5a3 --- /dev/null +++ b/code/modules/unit_tests/focus_only_tests.dm @@ -0,0 +1,19 @@ +/// These tests perform no behavior of their own, and have their tests offloaded onto other procs. +/// This is useful in cases like in build_appearance_list where we want to know if any fail, +/// but is not useful to right a test for. +/// This file exists so that you can change any of these to TEST_FOCUS and only check for that test. +/// For example, change /datum/unit_test/focus_only/invalid_overlays to TEST_FOCUS(/datum/unit_test/focus_only/invalid_overlays), +/// and you will only test the check for invalid overlays in appearance building. +/datum/unit_test/focus_only + +/// Checks that every overlay passed into build_appearance_list exists in the icon +/datum/unit_test/focus_only/invalid_overlays + +/// Checks that every icon sent to the research_designs spritesheet is valid +/datum/unit_test/focus_only/invalid_research_designs + +/// Checks that every icon sent to vending machines is valid +/datum/unit_test/focus_only/invalid_vending_machine_icon_states + +/// Checks that smoothing_groups and canSmoothWith are properly sorted in /atom/Initialize +/datum/unit_test/focus_only/sorted_smoothing_groups diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index fddcaa74f08..06347954e7e 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -17,6 +17,16 @@ GLOBAL_VAR(test_log) /// When unit testing, all logs sent to log_mapping are stored here and retrieved in log_mapping unit test. GLOBAL_LIST_EMPTY(unit_test_mapping_logs) +/// The name of the test that is currently focused. +/// Use the PERFORM_ALL_TESTS macro instead. +GLOBAL_VAR_INIT(focused_test, focused_test()) + +/proc/focused_test() + for (var/datum/unit_test/unit_test as anything in subtypesof(/datum/unit_test)) + if (initial(unit_test.focus)) + return unit_test + return null + /datum/unit_test //Bit of metadata for the future maybe var/list/procs_tested @@ -125,6 +135,9 @@ GLOBAL_LIST_EMPTY(unit_test_mapping_logs) log_world("::[priority] file=[file],line=[line],title=[map_name]: [type]::[annotation_text]") /proc/RunUnitTest(test_path, list/test_results) + if (ispath(test_path, /datum/unit_test/focus_only)) + return + var/datum/unit_test/test = new test_path GLOB.current_test = test diff --git a/tgstation.dme b/tgstation.dme index 374eb2b0768..032826f0540 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -205,6 +205,7 @@ #include "code\__DEFINES\turbine_defines.dm" #include "code\__DEFINES\turfs.dm" #include "code\__DEFINES\typeids.dm" +#include "code\__DEFINES\unit_tests.dm" #include "code\__DEFINES\uplink.dm" #include "code\__DEFINES\vehicles.dm" #include "code\__DEFINES\verb_manager.dm"