diff --git a/aurorastation.dme b/aurorastation.dme index 50fa5c471a2..6a76f1a0f7d 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -21,6 +21,7 @@ #include "code\__defines\_compile_options.dm" #include "code\__defines\_layers.dm" #include "code\__defines\_macros.dm" +#include "code\__defines\_unit_tests.dm" #include "code\__defines\accessories.dm" #include "code\__defines\admin.dm" #include "code\__defines\antagonist.dm" @@ -47,6 +48,7 @@ #include "code\__defines\lighting.dm" #include "code\__defines\lists.dm" #include "code\__defines\machinery.dm" +#include "code\__defines\manual_unit_testing.dm" #include "code\__defines\master_controller.dm" #include "code\__defines\materials.dm" #include "code\__defines\math_physics.dm" @@ -3168,6 +3170,7 @@ #include "code\modules\xgm\xgm_gas_mixture.dm" #include "code\unit_tests\chemistry_tests.dm" #include "code\unit_tests\cooking_tests.dm" +#include "code\unit_tests\create_and_destroy.dm" #include "code\unit_tests\equipment_tests.dm" #include "code\unit_tests\foundation_tests.dm" #include "code\unit_tests\gamemode_tests.dm" @@ -3186,6 +3189,8 @@ #include "code\unit_tests\species_tests.dm" #include "code\unit_tests\sql_tests.dm" #include "code\unit_tests\ss_test.dm" +#include "code\unit_tests\subsystem_init.dm" +#include "code\unit_tests\timer_sanity.dm" #include "code\unit_tests\unit_test.dm" #include "code\unit_tests\vueui_tests.dm" #include "code\unit_tests\zas_tests.dm" diff --git a/code/__defines/_unit_tests.dm b/code/__defines/_unit_tests.dm new file mode 100644 index 00000000000..8ebdb2ebad4 --- /dev/null +++ b/code/__defines/_unit_tests.dm @@ -0,0 +1,73 @@ +//include unit test files in this module in this ifdef +//Keep this sorted alphabetically + +// #if defined(UNIT_TESTS) || defined(SPACEMAN_DMM) //tgstation style, not relevant for us, for now + +/// Constants indicating unit test completion status +#define UNIT_TEST_FAILED null +#define UNIT_TEST_PASSED 1 +#define UNIT_TEST_SKIPPED 2 //Currently not implemented + +/** + * Output colouring macros, ANSI as per https://gist.github.com/stevewithington/b1b620b5bc9252e2c32e2cad35efbf83 + */ + +/// Underlined Blue +#define TEST_OUTPUT_HI_BLUE(text) "\x1B\x5B0;94m[text]\x1B\x5B0m" +/// High Intensity Cyan +#define TEST_OUTPUT_U_CYAN(text) "\x1B\x5B4;36m[text]\x1B\x5B0m" + + +/** + * Macros used to log the Unit Test messages + */ + +#define TEST_FAIL(reason) (fail(reason || "No reason", __FILE__, __LINE__)) +#define TEST_PASS(reason) (pass(reason || "No reason", __FILE__, __LINE__)) + +/// Logs a warning message, to be used when something is important to be known to the reader, like a test that did not run, +/// but not a failure or something that necessarily indicates an issue +#define TEST_WARN(message) warn(##message, __FILE__, __LINE__) + + +/// Logs a notice, something that is good to be known to a scrutinizing eye, without being obnoxious +/// Do not use this with long lists or internals that most people would never care about for the vast majority of the time +#define TEST_NOTICE(message) notice(##message, __FILE__, __LINE__) + + +/// Logs debug messages of the test run, this is NOT normally visible in GitHub, the test has to be run in debug mode (on the GitHub actions) for that. +/// To be used to log debugging, internals information +#define TEST_DEBUG(message) debug(##message, __FILE__, __LINE__) + +/// Groups management +#define TEST_GROUP_OPEN(groupname) world.log << TEST_OUTPUT_HI_BLUE("----> UNIT TEST \[[groupname]\] <----") //world.log << "::group::"+##name <-- if we want to switch back to github auto-grouping +#define TEST_GROUP_CLOSE(message) world.log << TEST_OUTPUT_HI_BLUE("\n") //world.log << ##message + "\n::endgroup::" <-- if we want to switch back to github auto-grouping + +/// Asserts that a condition is true +#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return fail("Assertion failed: [reason || "No reason"]", __FILE__, __LINE__) } + +/// Asserts that a parameter is not null +#define TEST_ASSERT_NOTNULL(a, reason) if (isnull(a)) { return fail("Expected non-null value: [reason || "No reason"]", __FILE__, __LINE__) } + +/// Asserts that a parameter is null +#define TEST_ASSERT_NULL(a, reason) if (!isnull(a)) { return fail("Expected null value but received [a]: [reason || "No reason"]", __FILE__, __LINE__) } + +/// Asserts that the two parameters passed are equal +/// Optionally allows an additional message in the case of a failure +#define TEST_ASSERT_EQUAL(a, b, message) do { \ + var/lhs = ##a; \ + var/rhs = ##b; \ + if (lhs != rhs) { \ + return fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \ + } \ +} while (FALSE) + +/// Asserts that the two parameters passed are not equal +/// Optionally allows an additional message in the case of a failure +#define TEST_ASSERT_NOTEQUAL(a, b, message) do { \ + var/lhs = ##a; \ + var/rhs = ##b; \ + if (lhs == rhs) { \ + return fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \ + } \ +} while (FALSE) diff --git a/code/__defines/manual_unit_testing.dm b/code/__defines/manual_unit_testing.dm new file mode 100644 index 00000000000..c3d055a946a --- /dev/null +++ b/code/__defines/manual_unit_testing.dm @@ -0,0 +1,3 @@ +// !!! For manual use only, remember to recomment before PRing !!! +// #define UNIT_TEST +// #define MANUAL_UNIT_TEST diff --git a/code/__defines/master_controller.dm b/code/__defines/master_controller.dm index 0a16847f2fd..7c482a91419 100644 --- a/code/__defines/master_controller.dm +++ b/code/__defines/master_controller.dm @@ -46,6 +46,12 @@ /// SS_BACKGROUND has its own priority bracket, this overrides SS_TICKER's priority bump #define SS_BACKGROUND 4 +/// If this subsystem doesn't initialize, it should not report as a hard error in CI. +/// This should be used for subsystems that are flaky for complicated reasons, such as +/// the Lua subsystem, which relies on auxtools, which is unstable. +/// It should not be used simply to silence CI. +#define SS_OK_TO_FAIL_INIT (1 << 6) + //subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background)) #define SS_NO_TICK_CHECK 8 diff --git a/code/__defines/unit_testing.dm b/code/__defines/unit_testing.dm index 88eebd5aec1..8040c16b844 100644 --- a/code/__defines/unit_testing.dm +++ b/code/__defines/unit_testing.dm @@ -3,6 +3,8 @@ * This file is used by Travis to indicate that Unit Tests are to be ran. * Do not add anything but the UNIT_TEST definition here as it will be overwritten by Travis when running tests. * + * This is expected to not be included by default, if you need to set this manually, use the code\__defines\manual_unit_testing.dm file instead. + * * * Should you wish to edit set UNIT_TEST to 1 like so: * #define UNIT_TEST 1 diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm index 5343b8dc86e..d09e8d78251 100644 --- a/code/_helpers/logging.dm +++ b/code/_helpers/logging.dm @@ -196,9 +196,6 @@ game_log("NTSL", text) send_gelf_log(text, "[time_stamp()]: [text]", severity, "NTSL", additional_data = list("_ckey" = ckey)) -/proc/log_unit_test(text) - world.log << "## UNIT_TEST ##: [text]" - /proc/log_exception(exception/e) if (config.log_runtime) if (config.log_runtime == 2) diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm index 201c313744c..8a155215a2c 100644 --- a/code/_onclick/hud/ability_screen_objects.dm +++ b/code/_onclick/hud/ability_screen_objects.dm @@ -311,4 +311,4 @@ A.name = object_given.name ability_objects.Add(A) if(my_mob.client) - toggle_open(2) //forces the icons to refresh on screen \ No newline at end of file + toggle_open(2) //forces the icons to refresh on screen diff --git a/code/controllers/subsystems/garbage-debug.dm b/code/controllers/subsystems/garbage-debug.dm index d2f95ac5c9d..cf7840b5844 100644 --- a/code/controllers/subsystems/garbage-debug.dm +++ b/code/controllers/subsystems/garbage-debug.dm @@ -837,7 +837,7 @@ SearchVar(can_enter_vent_with) SearchVar(ALL_ANTIGENS) SearchVar(all_unit_tests_passed) - SearchVar(failed_unit_tests) + SearchVar(unit_tests_failures) SearchVar(total_unit_tests) SearchVar(ascii_esc) SearchVar(ascii_red) diff --git a/code/controllers/subsystems/spatial_gridmap.dm b/code/controllers/subsystems/spatial_gridmap.dm index 30a03802b68..f59438db0ad 100644 --- a/code/controllers/subsystems/spatial_gridmap.dm +++ b/code/controllers/subsystems/spatial_gridmap.dm @@ -365,6 +365,13 @@ ///find the spatial map cell that target belongs to, then add target's important_recusive_contents to it. ///make sure to provide the turf new_target is "in" /datum/controller/subsystem/spatial_grid/proc/enter_cell(atom/movable/new_target, turf/target_turf) + + // This contraption only applies to unit tests, as during the destroy phase some have an MMI machine that is being deleted + #ifdef UNIT_TEST + if(QDELETED(new_target) && istype(new_target, /obj/item/organ/internal/mmi_holder)) + return + #endif + if(QDELETED(new_target)) CRASH("qdeleted or null target trying to enter the spatial grid!") diff --git a/code/game/atoms_init.dm b/code/game/atoms_init.dm index bce1d31b461..4a18b401335 100644 --- a/code/game/atoms_init.dm +++ b/code/game/atoms_init.dm @@ -39,10 +39,10 @@ var/turf/T = loc T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways. -#ifdef AO_USE_LIGHTING_OPACITY + #ifdef AO_USE_LIGHTING_OPACITY if (!mapload) T.regenerate_ao() -#endif + #endif if (update_icon_on_init) queue_icon_update() diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index 0d28b9b2b52..1f6089cc3d8 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -52,7 +52,7 @@ if(B != src) if (B.blood_DNA) blood_DNA |= B.blood_DNA.Copy() - qdel(B) + QDEL_IN(B, 1 SECOND) drytime = DRYING_TIME * (amount+1) bleed_time = world.time if (dries) diff --git a/code/modules/mining/coins.dm b/code/modules/mining/coins.dm index ec23024c186..68a2715a862 100644 --- a/code/modules/mining/coins.dm +++ b/code/modules/mining/coins.dm @@ -17,6 +17,7 @@ var/sides = 2 var/cmineral = null var/last_flip = 0 //Spam limiter + /obj/item/coin/New() randpixel_xy() diff --git a/code/modules/research/xenoarchaeology/finds/finds.dm b/code/modules/research/xenoarchaeology/finds/finds.dm index 2c8fa676fa1..886da395609 100644 --- a/code/modules/research/xenoarchaeology/finds/finds.dm +++ b/code/modules/research/xenoarchaeology/finds/finds.dm @@ -553,7 +553,7 @@ if(talkative) new_item.talking_atom = new(new_item) - qdel(src) + QDEL_IN(src, 1 SECOND) else if(talkative) src.talking_atom = new(src) diff --git a/code/unit_tests/chemistry_tests.dm b/code/unit_tests/chemistry_tests.dm index 672a86af65a..24a763517fc 100644 --- a/code/unit_tests/chemistry_tests.dm +++ b/code/unit_tests/chemistry_tests.dm @@ -15,12 +15,12 @@ datum/unit_test/specific_heat/start_test() for(var/reagent in GET_SINGLETON_SUBTYPE_MAP(/singleton/reagent/)) var/singleton/reagent/R = reagent if(!SSchemistry.has_valid_specific_heat(R)) - log_unit_test("[ascii_red][reagent] lacks a proper specific heat value![ascii_reset]") + TEST_FAIL("[reagent] lacks a proper specific heat value!") error_count++ if(error_count) - fail("[error_count] reagents(s) found without a proper specific heat value. Assign a specific heat value or make a recipe with these reagents as the final product.") + TEST_FAIL("[error_count] reagents(s) found without a proper specific heat value. Assign a specific heat value or make a recipe with these reagents as the final product.") else - pass("All reagents have a specific heat value.") + TEST_PASS("All reagents have a specific heat value.") return 1 diff --git a/code/unit_tests/cooking_tests.dm b/code/unit_tests/cooking_tests.dm index 7390bbaeef4..870b0988003 100644 --- a/code/unit_tests/cooking_tests.dm +++ b/code/unit_tests/cooking_tests.dm @@ -63,9 +63,7 @@ if(!tags_in_use[tag]) // is unused if(print_all_unused_tags) var/lstr = english_list(tags_available[tag]) - log_unit_test( - "[ascii_yellow]--------------- Unused '[tag]', defined by [lstr].[ascii_reset]" - ) + TEST_WARN(" Unused '[tag]', defined by [lstr].") else n_found += 1 @@ -74,22 +72,20 @@ if(length(not_found)) for (var/tag in not_found) var/lstr = english_list(tags_required[tag]) - log_unit_test( - "[ascii_red]--------------- Undefined '[tag]', required by [lstr]![ascii_reset]" - ) + TEST_FAIL("Undefined '[tag]', required by [lstr]!") var/msg = "[n_affected] of [length(recipes)] could not find [length(not_found)] tags!" if(n_unused) msg += " With [n_unused] unsued tags found." else msg += " With no unused tags." - fail(msg) + TEST_FAIL(msg) else var/msg = "All [length(recipes)] recipes could find all [n_found] needed tags!" if(n_unused) msg += " With [n_unused] unsued tags found." else msg += " With no unused tags." - pass(msg) + TEST_PASS(msg) return 1 diff --git a/code/unit_tests/create_and_destroy.dm b/code/unit_tests/create_and_destroy.dm new file mode 100644 index 00000000000..b43747ad15a --- /dev/null +++ b/code/unit_tests/create_and_destroy.dm @@ -0,0 +1,252 @@ +///Delete one of every type, sleep a while, then check to see if anything has gone fucky +/datum/unit_test/create_and_destroy + name = "Create and Destroy Test" + var/result = null + +// var/datum/running_create_and_destroy = FALSE +/datum/unit_test/create_and_destroy/start_test() + //We'll spawn everything here + var/turf/spawn_at = locate() + + /** + * EXCLUSIONS FROM THE TEST + * + * This is to be used when there's no other possible way to make this test work, to exclude a specific path from being scrutinized + * by this unit test. + * + * Any and all additions should be heavily scrutinized, this test exists to try to catch issues and bypassing it, barring the + * most extenous circumstances, is NOT preferable, and should only be resorted to for when all the other options are exhausted. + */ + + // Specific paths excluded + var/list/ignore = list( + //Never meant to be created, errors out the ass for mobcode reasons + /mob/living/carbon, + //Internal organs + /obj/item/organ/external, + // Requires an organ to init, so would not work here without snowflake code + /obj/item/device/augment_implanter, + // Wants to be put in hand on creation, so would not work here + /obj/item/device/radiojammer/improvised, + // Requires a path of some sort to init + /obj/item/storage/bag/stockparts_box/telecomms, + // Paint fails on init, probably not because of us, whoever wrote the chemistry of it have probably also eat the leaded one + /obj/item/reagent_containers/glass/paint, + // Probably not our fault too + /obj/item/reagent_containers/pill/pouch_pill, + // Cannot exist alone, as it's cut from a plant, and the update without a plant source fails + /obj/item/seeds/cutting, + + /obj/item/reagent_crystal, + + /obj/machinery/portable_atmospherics/hydroponics/soil/invisible, + + // Requires an operating table + /obj/machinery/computer/operating, + + // Requires to pick an output at init + /obj/machinery/appliance/mixer/, + + // Requires an AI + /obj/machinery/ai_powersupply, + + // Requires a player + /obj/screen/new_player/selection/join_game, + + // Requires to make a sound based on client pref in the announcement + /obj/effect/portal/revenant, + + // Wants a master + /obj/effect/beam/i_beam, + + // Wants a master + /obj/effect/dummy/chameleon, + + // Wants a parent + /obj/effect/plastic_explosive, + /obj/effect/temp_visual/incorporeal_mech, + + /obj/effect/liquid, + + // Generates EMP logs without a source + /obj/effect/temporary_effect/pulse/pulsar, + + /obj/effect/mineral, + + /obj/structure/largecrate/animal, + + /obj/turbolift_map_holder, + + /atom/movable/afterimage, + + /mob/living/announcer, + + /mob/abstract/dview, + + /obj/structure/mech_wreckage/powerloader, + + // Sleeps in init + /mob/living/carbon/human/terminator, + + // Mysterious failure in init, with gcdelete -1 + /mob/living/carbon/human/terminator, + + /obj/spellbutton, + + /obj/screen/click_catcher, + /obj/screen/new_player/selection/polls, + + ) + + // Paths and all the subpaths excluded + + //Needs a holodeck area linked to it which is not guarenteed to exist and technically is supposed to have a 1:1 relationship with computer anyway. + ignore += typesof(/obj/machinery/computer/HolodeckControl) + + // Spells require an owner, which would not work here + ignore += typesof(/obj/item/spell) + + // Groins fail for all subspecies + ignore += typesof(/obj/item/organ/external/groin) + + ignore += typesof(/obj/item/organ/internal) + + // The grab objects, could never work here + ignore += typesof(/obj/item/grab) + + // Robot modules, requires a robot + ignore += typesof(/obj/item/robot_module) + + // Requires a shuttle + ignore += typesof(/obj/machinery/computer/shuttle_control) + + // Requires a weapon attached + ignore += typesof(/obj/machinery/ammunition_loader) + + // Requires others of its components at init + ignore += typesof(/obj/machinery/gravity_generator/main/station) + + // Requires an owner's client + ignore += typesof(/obj/screen/psi) + + // Requires material on creation + ignore += typesof(/obj/effect/overlay/burnt_wall) + + ignore += typesof(/obj/random) + + // Map effects fuckery + ignore += typesof(/obj/effect/map_effect) + ignore += typesof(/obj/effect/shuttle_landmark) + ignore += typesof(/obj/effect/overmap/visitable) + ignore += typesof(/obj/effect/mazegen) + ignore += typesof(/obj/effect/ghostspawpoint) + + + ignore += typesof(/obj/turbolift_map_holder) + ignore += typesof(/atom/movable/z_observer) + ignore += typesof(/stat_rig_module) + ignore += typesof(/mob/living/silicon) + ignore += typesof(/obj/structure/ship_weapon_dummy) + ignore += typesof(/turf/simulated/floor/beach/water) + ignore += typesof(/mob/living/heavy_vehicle) + ignore += typesof(/obj/singularity/narsie) + ignore += typesof(/obj/screen/ability) + + // Requires something in icon update or runtimes + ignore += typesof(/obj/item/gun/energy/gun/nuclear) + + /** + * END EXCLUSIONS OF THE TEST + */ + + var/list/cached_contents = spawn_at.contents.Copy() + var/original_turf_type = spawn_at.type + var/original_baseturf = islist(spawn_at.baseturf) ? spawn_at.baseturf:Copy() : spawn_at.baseturf + var/original_baseturf_count = length(original_baseturf) + + // /datum/running_create_and_destroy = TRUE + for(var/type_path in typesof(/atom/movable, /turf) - ignore) //No areas please + + TEST_DEBUG("[name]: now creating and destroying: [type_path]") + + if(ispath(type_path, /turf)) + spawn_at.ChangeTurf(type_path) + //We change it back to prevent baseturfs stacking and hitting the limit + spawn_at.ChangeTurf(original_turf_type) + if(original_baseturf_count != length(spawn_at.baseturf)) + TEST_FAIL("[type_path] changed the amount of baseturfs from [original_baseturf_count] to [length(spawn_at.baseturf)]; [english_list(original_baseturf)] to [islist(spawn_at.baseturf) ? english_list(spawn_at.baseturf) : spawn_at.baseturf]") + // //Warn if it changes again + original_baseturf = islist(spawn_at.baseturf) ? spawn_at.baseturf:Copy() : spawn_at.baseturf + original_baseturf_count = length(original_baseturf) + else + var/atom/creation = new type_path(spawn_at) + if(QDELETED(creation)) + continue + //Go all in + qdel(creation, force = TRUE) + //This will hold a ref to the last thing we process unless we set it to null + //Yes byond is fucking sinful + creation = null + + //There's a lot of stuff that either spawns stuff in on create, or removes stuff on destroy. Let's cut it all out so things are easier to deal with + var/list/to_del = spawn_at.contents - cached_contents + if(length(to_del)) + for(var/atom/to_kill in to_del) + qdel(to_kill) + + //Hell code, we're bound to have ended the round somehow so let's stop if from ending while we work + SSticker.delay_end = TRUE + //Clear it, just in case + cached_contents.Cut() + + //Now that we've qdel'd everything, let's sleep until the gc has processed all the shit we care about + var/time_needed = SSgarbage.collection_timeout + var/start_time = world.time + var/garbage_queue_processed = FALSE + + sleep(time_needed) + while(!garbage_queue_processed) + var/list/queue_to_check = SSgarbage.queue + //How the hell did you manage to empty this? Good job! + if(!length(queue_to_check)) + garbage_queue_processed = TRUE + break + + //Pull out the time we deld at + var/qdeld_at = SSgarbage.queue[queue_to_check[1]] + //If we've found a packet that got del'd later then we finished, then all our shit has been processed + if(qdeld_at > start_time) + garbage_queue_processed = TRUE + break + + if(world.time > start_time + time_needed + 30 MINUTES) //If this gets us gitbanned I'm going to laugh so hard + result = TEST_FAIL("Something has gone horribly wrong, the garbage queue has been processing for well over 30 minutes. What the hell did you do") + break + + //Immediately fire the gc right after + SSgarbage.next_fire = 1 + //Unless you've seriously fucked up, queue processing shouldn't take "that" long. Let her run for a bit, see if anything's changed + sleep(20 SECONDS) + + //Alright, time to see if anything messed up + var/list/cache_for_sonic_speed = SSgarbage.didntgc + for(var/path in typesof(/atom/movable, /turf) - ignore) + var/times = cache_for_sonic_speed[path] + if(times) + result = TEST_FAIL("[path] hard deleted [times] times.") + + cache_for_sonic_speed = SSatoms.BadInitializeCalls + for(var/path in cache_for_sonic_speed) + var/fails = cache_for_sonic_speed[path] + if(fails & BAD_INIT_NO_HINT) + result = TEST_FAIL("[path] didn't return an Initialize hint") + if(fails & BAD_INIT_QDEL_BEFORE) + result = TEST_FAIL("[path] qdel'd in New()") + if(fails & BAD_INIT_SLEPT) + result = TEST_FAIL("[path] slept during Initialize()") + + SSticker.delay_end = FALSE + //This shouldn't be needed, but let's be polite + SSgarbage.collection_timeout = initial(SSgarbage.collection_timeout) + + return result ? result : TEST_PASS("All paths are created and destroyed successfully, without hard deletions or other unwanted behaviors") diff --git a/code/unit_tests/equipment_tests.dm b/code/unit_tests/equipment_tests.dm index e720df720a4..b94cbdfb8a8 100644 --- a/code/unit_tests/equipment_tests.dm +++ b/code/unit_tests/equipment_tests.dm @@ -12,10 +12,10 @@ datum/unit_test/vision_glasses/ datum/unit_test/vision_glasses/start_test() var/list/test = create_test_mob_with_mind(null, /mob/living/carbon/human) if(isnull(test)) - fail("Check Runtimed in Mob creation") + TEST_FAIL("Check Runtimed in Mob creation") if(test["result"] == FAILURE) - fail(test["msg"]) + TEST_FAIL(test["msg"]) async = 0 return 0 @@ -34,14 +34,14 @@ datum/unit_test/vision_glasses/check_result() return 0 if(isnull(H.glasses)) - fail("Mob doesn't have glasses on") + TEST_FAIL("Mob doesn't have glasses on") H.handle_vision() // Because Life has a client check that bypasses updating vision if(H.see_invisible == expectation) - pass("Mob See invisible is [H.see_invisible]") + TEST_PASS("Mob See invisible is [H.see_invisible]") else - fail("Mob See invisible is [H.see_invisible] / expected [expectation]") + TEST_FAIL("Mob See invisible is [H.see_invisible] / expected [expectation]") return 1 diff --git a/code/unit_tests/foundation_tests.dm b/code/unit_tests/foundation_tests.dm index 3aa3de26516..1dd0b35389b 100644 --- a/code/unit_tests/foundation_tests.dm +++ b/code/unit_tests/foundation_tests.dm @@ -13,9 +13,9 @@ datum/unit_test/foundation/step_shall_return_true_on_success/start_test() var/obj_step_result = TestStep(/obj) if(mob_step_result && obj_step_result) - pass("step() returned true.") + TEST_PASS("step() returned true.") else - fail("step() did not return true: Mob result: [mob_step_result] - Obj result: [obj_step_result].") + TEST_FAIL("step() did not return true: Mob result: [mob_step_result] - Obj result: [obj_step_result].") return 1 diff --git a/code/unit_tests/gamemode_tests.dm b/code/unit_tests/gamemode_tests.dm index 1f0a9d6f2e2..3a05bda93d0 100644 --- a/code/unit_tests/gamemode_tests.dm +++ b/code/unit_tests/gamemode_tests.dm @@ -13,23 +13,23 @@ var/min_antag_count = 0 for(var/antag_type in GM.antag_tags) var/datum/antagonist/A = all_antag_types[antag_type] - + if(GM.require_all_templates) min_antag_count += A.initial_spawn_req else min_antag_count = max(min_antag_count, A.initial_spawn_req) if(min_antag_count != GM.required_enemies) - failed += "[ascii_red]--------------- [GM] ([GM.type]) requires [GM.required_enemies] enemies but its antagonist roles require [min_antag_count] players!" + failed += "[GM] ([GM.type]) requires [GM.required_enemies] enemies but its antagonist roles require [min_antag_count] players!" if(min_antag_count > GM.required_players) - failed += "[ascii_red]--------------- [GM] ([GM.type]) requires [GM.required_players] players but its antagonist roles require [min_antag_count] players!" + failed += "[GM] ([GM.type]) requires [GM.required_players] players but its antagonist roles require [min_antag_count] players!" if(failed.len) - fail("Some gamemodes did not have high enough required_enemies or required_players.") + TEST_FAIL("Some gamemodes did not have high enough required_enemies or required_players.") for(var/failed_message in failed) - log_unit_test(failed_message) + TEST_FAIL(failed_message) else - pass("All gamemodes had suitable required_enemies and required_players.") + TEST_PASS("All gamemodes had suitable required_enemies and required_players.") - return 1 \ No newline at end of file + return 1 diff --git a/code/unit_tests/icon_tests.dm b/code/unit_tests/icon_tests.dm index 1a85524d5d8..a847f14e398 100644 --- a/code/unit_tests/icon_tests.dm +++ b/code/unit_tests/icon_tests.dm @@ -50,7 +50,7 @@ // Base icon state if(!(state in closet_states)) missing_states += 1 - log_unit_test("icon_state [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("icon_state [state] missing for [initial(closet_path.name)] -- ([closet_path])") // Non-animated door states if(!initial(closet_path.is_animating_door)) // Door icon @@ -58,55 +58,55 @@ state = "[initial(closet_path.icon_door)][closet_state_suffixes["door"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") else state = "[initial(closet_path.icon_state)][closet_state_suffixes["door"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") // Secure closet icon overlays if(initial(closet_path.secure)) // Emagged state = "[initial(closet_path.icon_door_overlay)][closet_state_suffixes["emag"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Emag'd icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Emag'd icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") // Locked state = "[initial(closet_path.icon_door_overlay)][closet_state_suffixes["locked"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Locked icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Locked icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") // Unlocked state = "[initial(closet_path.icon_door_overlay)][closet_state_suffixes["unlocked"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Unlocked icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Unlocked icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") // Opened if(initial(closet_path.icon_door_override)) state = "[initial(closet_path.icon_door)][closet_state_suffixes["opened"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Opened icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Opened icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") else state = "[initial(closet_path.icon_state)][closet_state_suffixes["opened"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Opened icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Opened icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") // Animated Door else state = "[initial(closet_path.icon_door) || initial(closet_path.icon_state)][closet_state_suffixes["door"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Animated door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Animated door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") state = "[initial(closet_path.icon_door) || initial(closet_path.icon_state)][closet_state_suffixes["back"]]" if(!(state in closet_states)) missing_states += 1 - log_unit_test("Animated door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") + TEST_FAIL("Animated door icon [state] missing for [initial(closet_path.name)] -- ([closet_path])") if(missing_states) - fail("[missing_states] closet icon state\s [missing_states == 1 ? "is" : "are"] missing.") + TEST_FAIL("[missing_states] closet icon state\s [missing_states == 1 ? "is" : "are"] missing.") else - pass("All related closet icon states exist.") + TEST_PASS("All related closet icon states exist.") return TRUE /datum/unit_test/icon_test/closets/mapped_closets_shall_have_invalid_icon_states @@ -124,12 +124,12 @@ continue invalid_states++ - log_unit_test("Mapped closet [C] at [C.x], [C.y], [C.z] had an invalid icon_state defined: [C.icon_state]!") + TEST_FAIL("Mapped closet [C] at [C.x], [C.y], [C.z] had an invalid icon_state defined: [C.icon_state]!") if(invalid_states) - fail("Found [invalid_states] / [checked_closets] mapped closets with invalid mapped icon states!") + TEST_FAIL("Found [invalid_states] / [checked_closets] mapped closets with invalid mapped icon states!") else - pass("All mapped closets had valid icon states.") + TEST_PASS("All mapped closets had valid icon states.") return TRUE @@ -143,9 +143,9 @@ var/list/rig_states = icon_states(R.icon) if(!(R.icon_state in rig_states)) - fail("[R.name]'s module icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s module icon_state isn't in its icon file.") if(!("[R.icon_state]_ba" in rig_states)) - fail("[R.name]'s on-back module icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s on-back module icon_state isn't in its icon file.") var/list/species_to_check = list("") // blank means default, human if(length(R.icon_supported_species_tags)) @@ -153,50 +153,50 @@ if(R.helm_type) if(!("[R.icon_state]_helmet" in rig_states)) - fail("[R.name]'s helmet icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s helmet icon_state isn't in its icon file.") if(!("[R.icon_state]_sealed_helmet" in rig_states)) - fail("[R.name]'s sealed helmet icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s sealed helmet icon_state isn't in its icon file.") if(R.suit_type) if(!("[R.icon_state]_suit" in rig_states)) - fail("[R.name]'s suit icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s suit icon_state isn't in its icon file.") if(!("[R.icon_state]_sealed_suit" in rig_states)) - fail("[R.name]'s sealed suit icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s sealed suit icon_state isn't in its icon file.") if(R.glove_type) if(!("[R.icon_state]_gloves" in rig_states)) - fail("[R.name]'s gloves icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s gloves icon_state isn't in its icon file.") if(!("[R.icon_state]_sealed_gloves" in rig_states)) - fail("[R.name]'s sealed gloves icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s sealed gloves icon_state isn't in its icon file.") if(R.boot_type) if(!("[R.icon_state]_shoes" in rig_states)) - fail("[R.name]'s shoes icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s shoes icon_state isn't in its icon file.") if(!("[R.icon_state]_sealed_shoes" in rig_states)) - fail("[R.name]'s sealed shoes icon_state isn't in its icon file.") + TEST_FAIL("[R.name]'s sealed shoes icon_state isn't in its icon file.") for(var/short in species_to_check) short = UNDERSCORE_OR_NULL(short) if(R.helm_type) if(!("[short][R.icon_state]_he" in rig_states)) - fail("[short] [R.name]'s helmet item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s helmet item_state isn't in its icon file.") if(!("[short][R.icon_state]_sealed_he" in rig_states)) - fail("[short] [R.name]'s sealed helmet item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s sealed helmet item_state isn't in its icon file.") if(R.suit_type) if(!("[short][R.icon_state]_su" in rig_states)) - fail("[short] [R.name]'s suit item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s suit item_state isn't in its icon file.") if(!("[short][R.icon_state]_sealed_su" in rig_states)) - fail("[short] [R.name]'s sealed suit item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s sealed suit item_state isn't in its icon file.") if(R.glove_type) if(!("[short][R.icon_state]_gl" in rig_states)) - fail("[short] [R.name]'s gloves item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s gloves item_state isn't in its icon file.") if(!("[short][R.icon_state]_sealed_gl" in rig_states)) - fail("[short] [R.name]'s sealed gloves item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s sealed gloves item_state isn't in its icon file.") if(R.boot_type) if(!("[short][R.icon_state]_sh" in rig_states)) - fail("[short] [R.name]'s shoes item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s shoes item_state isn't in its icon file.") if(!("[short][R.icon_state]_sealed_sh" in rig_states)) - fail("[short] [R.name]'s sealed shoes item_state isn't in its icon file.") + TEST_FAIL("[short] [R.name]'s sealed shoes item_state isn't in its icon file.") if(!reported) - pass("All hardsuits have their correct sprites.") + TEST_PASS("All hardsuits have their correct sprites.") return TRUE diff --git a/code/unit_tests/language_test.dm b/code/unit_tests/language_test.dm index 88e00b4fb89..e70d125e668 100644 --- a/code/unit_tests/language_test.dm +++ b/code/unit_tests/language_test.dm @@ -7,11 +7,11 @@ for(var/language_path in subtypesof(/datum/language)) var/datum/language/L = new language_path if(L.key in used_keys) - fail("[L.name]'s key, [L.key], is used multiple times!") + TEST_FAIL("[L.name]'s key, [L.key], is used multiple times!") continue used_keys += L.key - - if(!reported) - pass("All languages have unique keys.") - return TRUE \ No newline at end of file + if(!reported) + TEST_PASS("All languages have unique keys.") + + return TRUE diff --git a/code/unit_tests/map_tests.dm b/code/unit_tests/map_tests.dm index af945cdeacd..38f22a123d9 100644 --- a/code/unit_tests/map_tests.dm +++ b/code/unit_tests/map_tests.dm @@ -40,19 +40,19 @@ var/bad_msg = "[ascii_red]--------------- [A.name] ([A.type])" if(!A.apc && !is_type_in_typecache(A, exempt_from_apc)) - log_unit_test("[bad_msg] lacks an APC.[ascii_reset]") + TEST_FAIL("[bad_msg] lacks an APC.[ascii_reset]") bad_apc++ if(!A.air_scrub_info.len && !is_type_in_typecache(A, exempt_from_atmos)) - log_unit_test("[bad_msg] lacks an air scrubber.[ascii_reset]") + TEST_FAIL("[bad_msg] lacks an air scrubber.[ascii_reset]") bad_airs++ if(!A.air_vent_info.len && !is_type_in_typecache(A, exempt_from_atmos)) - log_unit_test("[bad_msg] lacks an air vent.[ascii_reset]") + TEST_FAIL("[bad_msg] lacks an air vent.[ascii_reset]") bad_airv++ if(!(locate(/obj/machinery/firealarm) in A) && !is_type_in_typecache(A, exempt_from_fire)) - log_unit_test("[bad_msg] lacks a fire alarm.[ascii_reset]") + TEST_FAIL("[bad_msg] lacks a fire alarm.[ascii_reset]") bad_fire++ if(bad_apc) @@ -65,9 +65,9 @@ fail_message += "\[[bad_fire]/[area_test_count]\] areas lacked a fire alarm.\n" if(length(fail_message)) - fail(fail_message) + TEST_FAIL(fail_message) else - pass("All \[[area_test_count]\] areas contained APCs, air scrubbers, air vents, and fire alarms.") + TEST_PASS("All \[[area_test_count]\] areas contained APCs, air scrubbers, air vents, and fire alarms.") return TRUE @@ -97,13 +97,13 @@ var/combined_dir = "[C.d1]-[C.d2]" if(combined_dir in dirs_checked) bad_tests++ - log_unit_test("[bad_msg] Contains multiple wires with same direction on top of each other.") + TEST_FAIL("[bad_msg] Contains multiple wires with same direction on top of each other.") dirs_checked.Add(combined_dir) if(bad_tests) - fail("\[[bad_tests] / [wire_test_count]\] Some turfs had overlapping wires going the same direction.") + TEST_FAIL("\[[bad_tests] / [wire_test_count]\] Some turfs had overlapping wires going the same direction.") else - pass("All \[[wire_test_count]\] wires had no overlapping cables going the same direction.") + TEST_PASS("All \[[wire_test_count]\] wires had no overlapping cables going the same direction.") return 1 @@ -127,12 +127,12 @@ if (above && above.is_hole) bad_tiles++ - log_unit_test("[ascii_red]--------------- [T.name] \[[T.x] / [T.y] / [T.z]\] Has no roof.[ascii_reset]") + TEST_FAIL("[T.name] \[[T.x] / [T.y] / [T.z]\] Has no roof.") if (bad_tiles) - fail("\[[bad_tiles] / [tiles_total]\] station turfs had no roof.") + TEST_FAIL("\[[bad_tiles] / [tiles_total]\] station turfs had no roof.") else - pass("All \[[tiles_total]\] station turfs had a roof.") + TEST_PASS("All \[[tiles_total]\] station turfs had a roof.") return 1 @@ -155,7 +155,7 @@ if (!ladder.target_up && !ladder.target_down) ladders_incomplete++ - log_unit_test("[ascii_red]--------------- [ladder.name] \[[ladder.x] / [ladder.y] / [ladder.z]\] Is incomplete.[ascii_reset]") + TEST_FAIL("[ladder.name] \[[ladder.x] / [ladder.y] / [ladder.z]\] Is incomplete.") continue var/bad = 0 @@ -167,12 +167,12 @@ if (bad) ladders_blocked++ - log_unit_test("[ascii_red]--------------- [ladder.name] \[[ladder.x] / [ladder.y] / [ladder.z]\] Is blocked in dirs:[(bad & BLOCKED_UP) ? " UP" : ""][(bad & BLOCKED_DOWN) ? " DOWN" : ""].[ascii_reset]") + TEST_FAIL("[ladder.name] \[[ladder.x] / [ladder.y] / [ladder.z]\] Is blocked in dirs:[(bad & BLOCKED_UP) ? " UP" : ""][(bad & BLOCKED_DOWN) ? " DOWN" : ""].") if (ladders_blocked || ladders_incomplete) - fail("\[[ladders_blocked + ladders_incomplete] / [ladders_total]\] ladders were bad.[ladders_blocked ? " [ladders_blocked] blocked." : ""][ladders_incomplete ? " [ladders_incomplete] incomplete." : ""]") + TEST_FAIL("\[[ladders_blocked + ladders_incomplete] / [ladders_total]\] ladders were bad.[ladders_blocked ? " [ladders_blocked] blocked." : ""][ladders_incomplete ? " [ladders_incomplete] incomplete." : ""]") else - pass("All [ladders_total] ladders were okay.") + TEST_PASS("All [ladders_total] ladders were okay.") return 1 @@ -190,12 +190,12 @@ checks++ if(istype(T, /turf/space) || istype(T, /turf/unsimulated/floor/asteroid) || isopenturf(T) || T.density) failed_checks++ - log_unit_test("Airlock [A] with bad turf at ([A.x],[A.y],[A.z]) in [T.loc].") + TEST_FAIL("Airlock [A] with bad turf at ([A.x],[A.y],[A.z]) in [T.loc].") if(failed_checks) - fail("\[[failed_checks] / [checks]\] Some doors had improper turfs below them.") + TEST_FAIL("\[[failed_checks] / [checks]\] Some doors had improper turfs below them.") else - pass("All \[[checks]\] doors have proper turfs below them.") + TEST_PASS("All \[[checks]\] doors have proper turfs below them.") return 1 @@ -213,15 +213,15 @@ firelock_increment += 1 if(firelock_increment > 1) failed_checks++ - log_unit_test("Double firedoor [F] at ([F.x],[F.y],[F.z]) in [T.loc].") + TEST_FAIL("Double firedoor [F] at ([F.x],[F.y],[F.z]) in [T.loc].") else if(istype(T, /turf/space) || istype(T, /turf/unsimulated/floor/asteroid) || isopenturf(T) || T.density) failed_checks++ - log_unit_test("Firedoor with bad turf at ([F.x],[F.y],[F.z]) in [T.loc].") + TEST_FAIL("Firedoor with bad turf at ([F.x],[F.y],[F.z]) in [T.loc].") if(failed_checks) - fail("\[[failed_checks] / [checks]\] Some firedoors were doubled up or had bad turfs below them.") + TEST_FAIL("\[[failed_checks] / [checks]\] Some firedoors were doubled up or had bad turfs below them.") else - pass("All \[[checks]\] firedoors have proper turfs below them and are not doubled up.") + TEST_PASS("All \[[checks]\] firedoors have proper turfs below them and are not doubled up.") return 1 @@ -240,7 +240,7 @@ checks++ if (plumbing.nodealert) failed_checks++ - log_unit_test("Unconnected [plumbing.name] located at [plumbing.x],[plumbing.y],[plumbing.z] ([get_area(plumbing.loc)])") + TEST_FAIL("Unconnected [plumbing.name] located at [plumbing.x],[plumbing.y],[plumbing.z] ([get_area(plumbing.loc)])") //Manifolds for (var/obj/machinery/atmospherics/pipe/manifold/pipe in world) @@ -249,7 +249,7 @@ checks++ if (!pipe.node1 || !pipe.node2 || !pipe.node3) failed_checks++ - log_unit_test("Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") + TEST_FAIL("Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") //Pipes for (var/obj/machinery/atmospherics/pipe/simple/pipe in world) @@ -258,7 +258,7 @@ checks++ if (!pipe.node1 || !pipe.node2) failed_checks++ - log_unit_test("Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") + TEST_FAIL("Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") next_turf: for(var/turf/T in turfs) @@ -270,12 +270,12 @@ for(var/connect_type in pipe.connect_types) connect_types[connect_type] += 1 if(connect_types[1] > 1 || connect_types[2] > 1 || connect_types[3] > 1) - log_unit_test("Overlapping pipe ([pipe.name]) located at [T.x],[T.y],[T.z] ([get_area(T)])") + TEST_FAIL("Overlapping pipe ([pipe.name]) located at [T.x],[T.y],[T.z] ([get_area(T)])") continue next_turf if(failed_checks) - fail("\[[failed_checks] / [checks]\] Some pipes are not properly connected or doubled up.") + TEST_FAIL("\[[failed_checks] / [checks]\] Some pipes are not properly connected or doubled up.") else - pass("All \[[checks]\] pipes are properly connected and not doubled up.") + TEST_PASS("All \[[checks]\] pipes are properly connected and not doubled up.") return 1 @@ -295,12 +295,12 @@ if(length(difflist(V.products, temp_V.products)) || length(difflist(V.contraband, temp_V.contraband)) || length(difflist(V.premium, temp_V.premium))) failed_checks++ - log_unit_test("Vending machine [V] at ([V.x],[V.y],[V.z] on [V.loc] has mapped-in products, contraband, or premium items.") + TEST_FAIL("Vending machine [V] at ([V.x],[V.y],[V.z] on [V.loc] has mapped-in products, contraband, or premium items.") if(failed_checks) - fail("\[[failed_checks] / [checks]\] Some vending machines have mapped-in product lists.") + TEST_FAIL("\[[failed_checks] / [checks]\] Some vending machines have mapped-in product lists.") else - pass("All \[[checks]\] vending machines have valid product lists.") + TEST_PASS("All \[[checks]\] vending machines have valid product lists.") return 1 @@ -330,12 +330,12 @@ var/list/failed_area_zlevels = list() for(var/turf/T as anything in invalid_turfs) failed_area_zlevels |= T.z - log_unit_test("Station area [A]: [invalid_turfs.len] turfs are not entirely mapped on station z-levels. Found turfs on non-station levels: [english_list(failed_area_zlevels)]") + TEST_FAIL("Station area [A]: [invalid_turfs.len] turfs are not entirely mapped on station z-levels. Found turfs on non-station levels: [english_list(failed_area_zlevels)]") if(failed_checks) - fail("\[[failed_checks] / [checks]\] Some station areas had turfs mapped outside station z-levels.") + TEST_FAIL("\[[failed_checks] / [checks]\] Some station areas had turfs mapped outside station z-levels.") else - pass("All \[[checks]\] station areas are correctly mapped only on station z-levels.") + TEST_PASS("All \[[checks]\] station areas are correctly mapped only on station z-levels.") return 1 diff --git a/code/unit_tests/mob_tests.dm b/code/unit_tests/mob_tests.dm index 297550305b7..14bd5effd34 100644 --- a/code/unit_tests/mob_tests.dm +++ b/code/unit_tests/mob_tests.dm @@ -32,7 +32,7 @@ datum/unit_test/mob_hear datum/unit_test/mob_hear/start_test() var/mobloc = pick(tdome1) if(!mobloc) - fail("Unable to find a location to create test mob") + TEST_FAIL("Unable to find a location to create test mob") return 0 for(var/mob/M in get_turf(mobloc)) QDEL_NULL(M) @@ -40,45 +40,45 @@ datum/unit_test/mob_hear/start_test() var/list/test_listener = create_test_mob_with_mind(mobloc, mob_type, TRUE) if(isnull(test_speaker) || isnull(test_listener)) - fail("Check Runtimed in Mob creation") + TEST_FAIL("Check Runtimed in Mob creation") return 0 if(test_speaker["result"] == FAILURE ) - fail(test_speaker["msg"]) + TEST_FAIL(test_speaker["msg"]) return 0 else if(test_listener ["result"] == FAILURE) - fail(test_listener["msg"]) + TEST_FAIL(test_listener["msg"]) return 0 var/mob/living/test/test_speaker_mob = locate(test_speaker["mobref"]) var/mob/living/test/test_listener_mob = locate(test_listener["mobref"]) if(isnull(test_speaker_mob) || isnull(test_listener_mob)) - fail("Test unable to set test mob from reference") + TEST_FAIL("Test unable to set test mob from reference") return 0 if(test_speaker_mob.stat) - fail("Test needs to be re-written, mob has a stat = [test_speaker_mob.stat]") + TEST_FAIL("Test needs to be re-written, mob has a stat = [test_speaker_mob.stat]") return 0 else if(test_listener_mob.stat) - fail("Test needs to be re-written, mob has a stat = [test_speaker_mob.stat]") + TEST_FAIL("Test needs to be re-written, mob has a stat = [test_speaker_mob.stat]") return 0 if(test_speaker_mob.sleeping || test_listener_mob.sleeping) - fail("Test needs to be re-written, mob is sleeping for some unknown reason") + TEST_FAIL("Test needs to be re-written, mob is sleeping for some unknown reason") return 0 var/message = "Test, can you hear me?" var/said = test_speaker_mob.say(message) if(said && test_listener_mob.heard) - pass("speech test complete, speaker said \"[message]\" and listener received it.") + TEST_PASS("speech test complete, speaker said \"[message]\" and listener received it.") return 1 else if(said) - fail("speaker said the words, but listener did not hear it. The message was \"[message]\", the difference were X: [test_listener_mob.loc.x - test_speaker_mob.loc.x], Y: [test_listener_mob.loc.y - test_speaker_mob.loc.y]") + TEST_FAIL("speaker said the words, but listener did not hear it. The message was \"[message]\", the difference were X: [test_listener_mob.loc.x - test_speaker_mob.loc.x], Y: [test_listener_mob.loc.y - test_speaker_mob.loc.y]") return 0 else - fail("speaker did not say the words \"[message]\"") + TEST_FAIL("speaker did not say the words \"[message]\"") return 0 datum/unit_test/human_breath @@ -109,9 +109,9 @@ datum/unit_test/human_breath/check_result() ending_oxyloss = damage_check(H, DAMAGE_OXY) if(starting_oxyloss < ending_oxyloss) - pass("Oxyloss = [ending_oxyloss]") + TEST_PASS("Oxyloss = [ending_oxyloss]") else - fail("Mob is not taking oxygen damage. Damange is [ending_oxyloss]") + TEST_FAIL("Mob is not taking oxygen damage. Damange is [ending_oxyloss]") return 1 // return 1 to show we're done and don't want to recheck the result. @@ -202,26 +202,26 @@ datum/unit_test/mob_damage/start_test() // Which makes checks impossible. if(isnull(test)) - fail("Check Runtimed in Mob creation") + TEST_FAIL("Check Runtimed in Mob creation") return 0 if(test["result"] == FAILURE) - fail(test["msg"]) + TEST_FAIL(test["msg"]) return 0 var/mob/living/carbon/human/H = locate(test["mobref"]) if(isnull(H)) - fail("Test unable to set test mob from reference") + TEST_FAIL("Test unable to set test mob from reference") return 0 if(H.stat) - fail("Test needs to be re-written, mob has a stat = [H.stat]") + TEST_FAIL("Test needs to be re-written, mob has a stat = [H.stat]") return 0 if(H.sleeping) - fail("Test needs to be re-written, mob is sleeping for some unknown reason") + TEST_FAIL("Test needs to be re-written, mob is sleeping for some unknown reason") return 0 // Damage the mob @@ -273,9 +273,9 @@ datum/unit_test/mob_damage/start_test() var/msg = "Damage taken: [ending_damage] out of [damage_amount] || expected: [expected_msg] \[Overall Health:[ending_health] (Initial: [initial_health])\]" if(failure) - fail(msg) + TEST_FAIL(msg) else - pass(msg) + TEST_PASS(msg) return 1 @@ -524,7 +524,7 @@ datum/unit_test/robot_module_icons datum/unit_test/robot_module_icons/start_test() var/failed = 0 if(!isicon(icon_file)) - fail("[icon_file] is not a valid icon file.") + TEST_FAIL("[icon_file] is not a valid icon file.") return 1 var/list/valid_states = icon_states(icon_file) @@ -535,13 +535,13 @@ datum/unit_test/robot_module_icons/start_test() for(var/i=1, i<=robot_modules.len, i++) var/bad_msg = "[ascii_red]--------------- [robot_modules[i]]" if(!(lowertext(robot_modules[i]) in valid_states)) - log_unit_test("[bad_msg] does not contain a valid icon state in [icon_file][ascii_reset]") + TEST_FAIL("[bad_msg] does not contain a valid icon state in [icon_file][ascii_reset]") failed=1 if(failed) - fail("Some icon states did not exist") + TEST_FAIL("Some icon states did not exist") else - pass("All modules had valid icon states") + TEST_PASS("All modules had valid icon states") return 1 diff --git a/code/unit_tests/object_tests.dm b/code/unit_tests/object_tests.dm index 9b42febe825..6c2f3b15d0b 100644 --- a/code/unit_tests/object_tests.dm +++ b/code/unit_tests/object_tests.dm @@ -24,11 +24,11 @@ if (unfound_types.len) for (var/t in unfound_types) - log_unit_test("[ascii_red]--------------- [unfound_types[t]] instances of [t] not found in SSmachinery.machinery.") + TEST_FAIL("[unfound_types[t]] instances of [t] not found in SSmachinery.machinery.") - fail("\[[unfound_types.len] / [all_types.len]\] mapped in machinery types were not found in SSmachinery.machinery.") + TEST_FAIL("\[[unfound_types.len] / [all_types.len]\] mapped in machinery types were not found in SSmachinery.machinery.") else - pass("All \[[all_types.len]\] mapped in machinery types were found in SSmachinery.machinery.") + TEST_PASS("All \[[all_types.len]\] mapped in machinery types were found in SSmachinery.machinery.") return 1 @@ -47,7 +47,7 @@ known_types += F.build_type if(known_types.len == length(uniquelist(known_types))) - pass("All flooring types had a unique or null build type.") + TEST_PASS("All flooring types had a unique or null build type.") else for(var/type in known_types) var/i = 0 @@ -55,8 +55,8 @@ if(flooring_type == type) i++ if(i != 1) - log_unit_test("[ascii_red]--------------- Flooring build_type [type] is non-unique; exists [i] times.") - fail("Found non-unique build_types in flooring decl.") + TEST_FAIL("Flooring build_type [type] is non-unique; exists [i] times.") + TEST_FAIL("Found non-unique build_types in flooring decl.") return TRUE @@ -72,13 +72,13 @@ for(var/k in p) vending_products += k if(!ispath(k, /obj)) - log_unit_test("Vending product [k] in vending machine [V] is not a subtype of /obj") + TEST_FAIL("Vending product [k] in vending machine [V] is not a subtype of /obj") else valid_keys += k if(length(valid_keys) == length(vending_products)) - pass("All vending products are /obj subtypes") + TEST_PASS("All vending products are /obj subtypes") else - fail("Some vending products are not /obj subtypes") + TEST_FAIL("Some vending products are not /obj subtypes") return TRUE diff --git a/code/unit_tests/observation_tests.dm b/code/unit_tests/observation_tests.dm index b641b07240d..a25d6bac917 100644 --- a/code/unit_tests/observation_tests.dm +++ b/code/unit_tests/observation_tests.dm @@ -15,9 +15,9 @@ datum/unit_test/observation/moved_observer_shall_register_on_follow/start_test() O.ManualFollow(H) if(is_listening_to_movement(H, O)) - pass("The observer is now following the mob.") + TEST_PASS("The observer is now following the mob.") else - fail("The observer is not following the mob.") + TEST_FAIL("The observer is not following the mob.") QDEL_IN(H, 10 SECONDS) QDEL_IN(O, 10 SECONDS) @@ -34,9 +34,9 @@ datum/unit_test/observation/moved_observer_shall_unregister_on_nofollow/start_te O.ManualFollow(H) O.stop_following() if(!is_listening_to_movement(H, O)) - pass("The observer is no longer following the mob.") + TEST_PASS("The observer is no longer following the mob.") else - fail("The observer is still following the mob.") + TEST_FAIL("The observer is still following the mob.") QDEL_IN(H, 10 SECONDS) QDEL_IN(O, 10 SECONDS) @@ -56,9 +56,9 @@ datum/unit_test/observation/moved_shall_registers_recursively_on_new_listener/st var/listening_to_closet = is_listening_to_movement(C, H) var/listening_to_human = is_listening_to_movement(H, O) if(listening_to_closet && listening_to_human) - pass("Recursive moved registration succesful.") + TEST_PASS("Recursive moved registration succesful.") else - fail("Recursive moved registration failed. Human listening to closet: [listening_to_closet] - Observer listening to human: [listening_to_human]") + TEST_FAIL("Recursive moved registration failed. Human listening to closet: [listening_to_closet] - Observer listening to human: [listening_to_human]") QDEL_IN(C, 10 SECONDS) QDEL_IN(H, 10 SECONDS) @@ -79,9 +79,9 @@ datum/unit_test/observation/moved_shall_registers_recursively_with_existing_list var/listening_to_closet = is_listening_to_movement(C, H) var/listening_to_human = is_listening_to_movement(H, O) if(listening_to_closet && listening_to_human) - pass("Recursive moved registration succesful.") + TEST_PASS("Recursive moved registration succesful.") else - fail("Recursive moved registration failed. Human listening to closet: [listening_to_closet] - Observer listening to human: [listening_to_human]") + TEST_FAIL("Recursive moved registration failed. Human listening to closet: [listening_to_closet] - Observer listening to human: [listening_to_human]") QDEL_IN(C, 10 SECONDS) QDEL_IN(H, 10 SECONDS) diff --git a/code/unit_tests/origins_tests.dm b/code/unit_tests/origins_tests.dm index 8e762d9493d..8ed66e2b948 100644 --- a/code/unit_tests/origins_tests.dm +++ b/code/unit_tests/origins_tests.dm @@ -6,21 +6,21 @@ var/list/singleton/origin_item/culture/all_cultures = GET_SINGLETON_SUBTYPE_MAP(/singleton/origin_item/culture) for(var/singleton/origin_item/culture/OC in all_cultures) if(!istext(OC.name)) - log_unit_test("Culture [OC.name] does not have a name!") + TEST_FAIL("Culture [OC.name] does not have a name!") failures++ if(!istext(OC.desc)) - log_unit_test("Culture [OC.name] does not have a description!") + TEST_FAIL("Culture [OC.name] does not have a description!") failures++ if(!islist(OC.possible_origins)) - log_unit_test("Culture [OC.name]'s possible_origins list is not a list!") + TEST_FAIL("Culture [OC.name]'s possible_origins list is not a list!") failures++ if(!length(OC.possible_origins)) - log_unit_test("Culture [OC.name] does not have any possible origins!") + TEST_FAIL("Culture [OC.name] does not have any possible origins!") failures++ if(failures) - fail("[failures] error(s) found.") + TEST_FAIL("[failures] error(s) found.") else - pass("All cultures are filled out properly.") + TEST_PASS("All cultures are filled out properly.") return TRUE /datum/unit_test/origins @@ -31,21 +31,21 @@ var/list/singleton/origin_item/origin/all_origins = GET_SINGLETON_SUBTYPE_MAP(/singleton/origin_item/origin) for(var/singleton/origin_item/origin/OI in all_origins) if(!istext(OI.name)) - log_unit_test("Origin [OI.name] does not have a name!") + TEST_FAIL("Origin [OI.name] does not have a name!") failures++ if(!istext(OI.desc)) - log_unit_test("Origin [OI.name] does not have a description!") + TEST_FAIL("Origin [OI.name] does not have a description!") failures++ if(!islist(OI.possible_accents) || !islist(OI.possible_citizenships) || !islist(OI.possible_religions)) - log_unit_test("Origin [OI.name] is missing at least one list in the possible accents, citizenships or religions!") + TEST_FAIL("Origin [OI.name] is missing at least one list in the possible accents, citizenships or religions!") failures++ if(!length(OI.possible_accents) || !length(OI.possible_citizenships) || !length(OI.possible_religions)) - log_unit_test("Origin [OI.name] is missing at least one entry in the possible accents, citizenships or religions lists!") + TEST_FAIL("Origin [OI.name] is missing at least one entry in the possible accents, citizenships or religions lists!") failures++ if(failures) - fail("[failures] error(s) found.") + TEST_FAIL("[failures] error(s) found.") else - pass("All origins are filled out properly.") + TEST_PASS("All origins are filled out properly.") return TRUE /datum/unit_test/accent_tags @@ -56,10 +56,10 @@ for(var/datum/accent/A in subtypesof(/datum/accent)) A = new() if(!istext(A.text_tag)) - log_unit_test("Accent tag [A.name] did not have a text tag or the type was inappropriate!") + TEST_FAIL("Accent tag [A.name] did not have a text tag or the type was inappropriate!") failures++ if(failures) - fail("[failures] errors found.") + TEST_FAIL("[failures] errors found.") else - pass("All accents have a text tag.") - return TRUE \ No newline at end of file + TEST_PASS("All accents have a text tag.") + return TRUE diff --git a/code/unit_tests/overmap_tests.dm b/code/unit_tests/overmap_tests.dm index 241f894178b..5258afb4f1b 100644 --- a/code/unit_tests/overmap_tests.dm +++ b/code/unit_tests/overmap_tests.dm @@ -11,9 +11,9 @@ invalid_overmap_types += omt if(invalid_overmap_types.len) - fail("Following /obj/effect/overmap types types have invalid colors: [english_list(invalid_overmap_types)]") + TEST_FAIL("Following /obj/effect/overmap types types have invalid colors: [english_list(invalid_overmap_types)]") else - pass("All /obj/effect/overmap types have a valid color") + TEST_PASS("All /obj/effect/overmap types have a valid color") return TRUE @@ -23,9 +23,9 @@ /datum/unit_test/overmap_ships_shall_have_entrypoints/start_test() for(var/obj/effect/overmap/visitable/ship/S in SSshuttle.initialized_sectors) if(length(S.entry_points) >= 4) - pass("[S.name] ([S.type]) has at least four entry points.") + TEST_PASS("[S.name] ([S.type]) has at least four entry points.") else - fail("[S.name] ([S.type]) does not have at least four entry points!") + TEST_FAIL("[S.name] ([S.type]) does not have at least four entry points!") return TRUE /datum/unit_test/overmap_ships_shall_have_class @@ -35,11 +35,11 @@ var/failures = 0 for(var/obj/effect/overmap/visitable/ship/S in SSshuttle.initialized_sectors) if(!length(S.class)) - fail("[S.name] ([S.type]) does not have a class defined.") + TEST_FAIL("[S.name] ([S.type]) does not have a class defined.") failures++ if(!length(S.designation)) - fail("[S.name] ([S.type]) does not have a designation defined.") + TEST_FAIL("[S.name] ([S.type]) does not have a designation defined.") failures++ if(!failures) - pass("All ships have a class and designation.") - return TRUE \ No newline at end of file + TEST_PASS("All ships have a class and designation.") + return TRUE diff --git a/code/unit_tests/power_tests.dm b/code/unit_tests/power_tests.dm index fdce30563ac..6165816e66f 100644 --- a/code/unit_tests/power_tests.dm +++ b/code/unit_tests/power_tests.dm @@ -35,16 +35,16 @@ if(other in searched) continue if(next.powernet != other.powernet) - log_unit_test("[ascii_red]--------------- Cable at ([next.x], [next.y], [next.z]) did not share powernet with connected neighbour at ([other.x], [other.y], [other.z])") + TEST_FAIL("Cable at ([next.x], [next.y], [next.z]) did not share powernet with connected neighbour at ([other.x], [other.y], [other.z])") failed++ to_search += other found_cables += searched if(failed) - fail("Found [failed] bad cables.") + TEST_FAIL("Found [failed] bad cables.") else - pass("All connected roundstart cables have matching powernets.") + TEST_PASS("All connected roundstart cables have matching powernets.") return 1 @@ -60,13 +60,13 @@ if(!found_apc) found_apc = APC continue - log_unit_test("[ascii_red]--------------- Duplicated APCs in area: [A.name]. #1: [log_info_line(found_apc)] #2: [log_info_line(APC)]") + TEST_FAIL("Duplicated APCs in area: [A.name]. #1: [log_info_line(found_apc)] #2: [log_info_line(APC)]") failed++ if(failed) - fail("Found [failed] duplicate APCs.") + TEST_FAIL("Found [failed] duplicate APCs.") else - pass("No areas with duplicated APCs have been found.") + TEST_PASS("No areas with duplicated APCs have been found.") return 1 /datum/unit_test/area_power_tally_accuracy @@ -82,10 +82,10 @@ for(var/i in 1 to length(old_values)) if(abs(old_values[i] - new_values[i]) > 1) // Round because there can in fact be roundoff error here apparently. failed = TRUE - log_unit_test("[ascii_red]--------------- The area [A.name] had improper power use values on the [channel_names[i]] channel: was [old_values[i]] but should be [new_values[i]].") + TEST_FAIL("The area [A.name] had improper power use values on the [channel_names[i]] channel: was [old_values[i]] but should be [new_values[i]].") if(failed) - fail("At least one area had improper power use values") + TEST_FAIL("At least one area had improper power use values") else - pass("All areas had accurate power use values.") + TEST_PASS("All areas had accurate power use values.") return 1 diff --git a/code/unit_tests/recipe_tests.dm b/code/unit_tests/recipe_tests.dm index cadbbb5d58c..ad390ab04dc 100755 --- a/code/unit_tests/recipe_tests.dm +++ b/code/unit_tests/recipe_tests.dm @@ -17,18 +17,18 @@ tested_count++ if(mat in D.materials) if(I.matter[mat] > D.materials[mat]) - fail("Design '[D.name]' costs less material '[mat]' ([D.materials[mat]]) than the product is worth ([I.matter[mat]]).") + TEST_FAIL("Design '[D.name]' costs less material '[mat]' ([D.materials[mat]]) than the product is worth ([I.matter[mat]]).") error_count++ else - fail("Design '[D.name]' does not require material '[mat]' even though the product is worth [I.matter[mat]].") + TEST_FAIL("Design '[D.name]' does not require material '[mat]' even though the product is worth [I.matter[mat]].") error_count++ qdel(I) qdel(D) if(error_count) - fail("[error_count] design error(s) found. Every research design should cost more than what its product is worth when recycled.") + TEST_FAIL("[error_count] design error(s) found. Every research design should cost more than what its product is worth when recycled.") else - pass("All [tested_count] research designs with recyclable products have correct material costs.") + TEST_PASS("All [tested_count] research designs with recyclable products have correct material costs.") return 1 @@ -55,16 +55,16 @@ var/item_matter_value = I.matter[mat] * R.res_amount var/consumed_matter_value = temp_matter[mat] * R.req_amount if(item_matter_value > consumed_matter_value) - fail("Recipe '[R.title]' on material '[D.name]' consumes less material '[mat]' ([R.req_amount] × [temp_matter[mat]] = [consumed_matter_value]) than the product is worth ([R.res_amount] × [I.matter[mat]] = [item_matter_value]).") + TEST_FAIL("Recipe '[R.title]' on material '[D.name]' consumes less material '[mat]' ([R.req_amount] × [temp_matter[mat]] = [consumed_matter_value]) than the product is worth ([R.res_amount] × [I.matter[mat]] = [item_matter_value]).") error_count++ else - warn("Recipe '[R.title]' on material '[D.name]' creates product with material '[mat]', but that material is not required by the recipe.") + TEST_WARN("Recipe '[R.title]' on material '[D.name]' creates product with material '[mat]', but that material is not required by the recipe.") qdel(I) qdel(D) if(error_count) - fail("[error_count] stack recipe error(s) found. Every stack recipe should cost more than what its product is worth when recycled.") + TEST_FAIL("[error_count] stack recipe error(s) found. Every stack recipe should cost more than what its product is worth when recycled.") else - pass("All [tested_count] stack recipes with recyclable /obj/item products have correct material costs.") + TEST_PASS("All [tested_count] stack recipes with recyclable /obj/item products have correct material costs.") return 1 diff --git a/code/unit_tests/shuttle_tests.dm b/code/unit_tests/shuttle_tests.dm index bfaf634a366..160113d1753 100644 --- a/code/unit_tests/shuttle_tests.dm +++ b/code/unit_tests/shuttle_tests.dm @@ -20,17 +20,17 @@ found_logging_home_location = TRUE if(initial(shuttle.current_location) && !found_current_location) - log_unit_test("Failed to find 'current_location' landmark for [shuttle].") + TEST_FAIL("Failed to find 'current_location' landmark for [shuttle].") failed++ if(initial(shuttle.landmark_transition) && !found_transition_location) - log_unit_test("Failed to find 'landmark_transition' landmark for [shuttle].") + TEST_FAIL("Failed to find 'landmark_transition' landmark for [shuttle].") failed++ if(initial(shuttle.logging_home_tag) && !found_logging_home_location) - log_unit_test("Failed to find 'logging_home_tag' landmark for [shuttle].") + TEST_FAIL("Failed to find 'logging_home_tag' landmark for [shuttle].") failed++ if(failed) - fail("[failed] shuttle transition and start location landmarks were not found.") + TEST_FAIL("[failed] shuttle transition and start location landmarks were not found.") else - pass("All shuttle transition and start location landmarks were found.") + TEST_PASS("All shuttle transition and start location landmarks were found.") return TRUE diff --git a/code/unit_tests/spawner_tests.dm b/code/unit_tests/spawner_tests.dm index 7a3261e3b79..243544dc990 100644 --- a/code/unit_tests/spawner_tests.dm +++ b/code/unit_tests/spawner_tests.dm @@ -7,7 +7,7 @@ datum/unit_test/template name = "Ghost Spawner Tests" // If it's a template leave the word "template" in it's name so it's not ran. - + datum/unit_test/template/start_test() var/list/ignore_spawners = list( @@ -27,13 +27,13 @@ datum/unit_test/template/start_test() continue //Check if we hae name, short_name and desc set if(!G.short_name || !G.name || !G.desc) - log_unit_test("[ascii_red]--------------- Invalid Spawner: Type:[G.type], Short-Name:[G.short_name], Name:[G.name]") + TEST_FAIL("Invalid Spawner: Type:[G.type], Short-Name:[G.short_name], Name:[G.name]") failed_checks++ if(failed_checks) - fail("\[[failed_checks] / [checks]\] Ghost Spawners are invalid") + TEST_FAIL("\[[failed_checks] / [checks]\] Ghost Spawners are invalid") else - pass("All Ghost Spawners are valid.") + TEST_PASS("All Ghost Spawners are valid.") return 1 diff --git a/code/unit_tests/species_tests.dm b/code/unit_tests/species_tests.dm index e8d4bb8e43a..6fb6fe030a3 100644 --- a/code/unit_tests/species_tests.dm +++ b/code/unit_tests/species_tests.dm @@ -18,9 +18,9 @@ if(failed_species.len) for(var/fail in failed_species) - fail("SPECIES: Invalid injection_mod var set on species: [english_list(failed_species)]") + TEST_FAIL("SPECIES: Invalid injection_mod var set on species: [english_list(failed_species)]") else - pass("SPECIES: All species had valid injection_mod vars set.") + TEST_PASS("SPECIES: All species had valid injection_mod vars set.") - return 1 \ No newline at end of file + return 1 diff --git a/code/unit_tests/sql_tests.dm b/code/unit_tests/sql_tests.dm index 9d537d28569..fa0ef8e9d24 100644 --- a/code/unit_tests/sql_tests.dm +++ b/code/unit_tests/sql_tests.dm @@ -3,15 +3,14 @@ /datum/unit_test/sql_preferences_columns/start_test() if(!config.sql_enabled) - log_unit_test("[ascii_yellow]--------------- Database not Configured - Skipping Preference Column UT") - return TRUE - - log_unit_test("[ascii_yellow]--------------- Database Configured - Running SQL Preference UTs") - + TEST_WARN("--------------- Database not Configured - Skipping Preference Column UT") + return UNIT_TEST_PASSED + + TEST_DEBUG("--------------- Database Configured - Running SQL Preference UTs") + if(!establish_db_connection(dbcon)) - log_unit_test("[ascii_red]--------------- Unable to establish database connection.") - fail("Database connection could not be established.") - return TRUE + TEST_FAIL("Database connection could not be established.") + return UNIT_TEST_FAILED var/faults = 0 var/valid_columns = list() @@ -31,9 +30,8 @@ get_cs.Execute(list("table" = T)) if (get_cs.ErrorMsg()) - log_unit_test("[ascii_red]--------------- SQL error encountered: [get_cs.ErrorMsg()].[ascii_reset]") - fail("SQL error encountered.") - return TRUE + TEST_FAIL("SQL error encountered: [get_cs.ErrorMsg()]") + return UNIT_TEST_FAILED valid_columns[T] = list() @@ -66,7 +64,7 @@ if (unfound.len) for (var/C in unfound) - log_unit_test("[ascii_red]--------------- load parameter '[C]' not found in any queries for '[A.name]':[A.type].[ascii_reset]") + TEST_FAIL("load parameter '[C]' not found in any queries for '[A.name]':[A.type].") faults++ temp.Cut() @@ -81,21 +79,21 @@ for (var/B in test_columns) var/list/valids = valid_columns[B] if (!valids || !valids.len) - log_unit_test("[ascii_red]--------------- table '[B]' referenced but not found for '[A.name]':[A.type].[ascii_reset]") + TEST_FAIL("table '[B]' referenced but not found for '[A.name]':[A.type].") faults++ continue for (var/C in test_columns[B]) if (!(C in valids)) - log_unit_test("[ascii_red]--------------- column '[C]' referenced but not in table '[B]' for item '[A.name]':[A.type].[ascii_reset]") + TEST_FAIL("column '[C]' referenced but not in table '[B]' for item '[A.name]':[A.type].") faults++ if (faults) - fail("\[[faults]\] faults found in the SQL preferences setup.") + TEST_FAIL("\[[faults]\] faults found in the SQL preferences setup.") + return UNIT_TEST_FAILED else - pass("No faults found in the SQL preferences setup.") - - return TRUE + TEST_PASS("No faults found in the SQL preferences setup.") + return UNIT_TEST_PASSED /datum/unit_test/sql_preferences_vars name = "SQL: Preferences Variables" @@ -126,12 +124,12 @@ total += test.len for (var/V in test) if (!(V in P.vars)) - log_unit_test("[ascii_red]--------------- variable '[V]' referenced by, but not found in preferences class variables, '[A.name]':[A.type].[ascii_reset]") + TEST_FAIL("variable '[V]' referenced by, but not found in preferences class variables, '[A.name]':[A.type].") faults++ if (faults) - fail("\[[faults] / [total]\] variable references found invalid in the SQL preferences setup.") + TEST_FAIL("\[[faults] / [total]\] variable references found invalid in the SQL preferences setup.") + return UNIT_TEST_FAILED else - pass("All \[[total]\] variable references found valid in the SQL preferences setup.") - - return TRUE + TEST_PASS("All \[[total]\] variable references found valid in the SQL preferences setup.") + return UNIT_TEST_PASSED diff --git a/code/unit_tests/ss_test.dm b/code/unit_tests/ss_test.dm index c60d75ec30d..028f94deab4 100644 --- a/code/unit_tests/ss_test.dm +++ b/code/unit_tests/ss_test.dm @@ -2,10 +2,15 @@ // It initializes last in the subsystem order, and queues // the tests to start about 20 seconds after init is done. +/** + * Wondering if you should change this to run the tests? NO! + * Because the preproc checks for this in other areas too, set it in code\__defines\manual_unit_testing.dm instead! + */ #ifdef UNIT_TEST /datum/controller/subsystem/unit_tests name = "Unit Tests" + var/datum/unit_test/UT = new // Use this to log things from outside where a specific unit_test is defined init_order = -1e6 // last. var/list/queue = list() var/list/async_tests = list() @@ -15,7 +20,7 @@ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY | RUNLEVEL_INIT /datum/controller/subsystem/unit_tests/Initialize(timeofday) - log_unit_test("Initializing Unit Testing") + UT.notice("Initializing Unit Testing", __FILE__, __LINE__) // //Start the Round. @@ -29,17 +34,17 @@ queue += D - log_unit_test("[queue.len] unit tests loaded.") + UT.notice("[queue.len] unit tests loaded.", __FILE__, __LINE__) ..() /datum/controller/subsystem/unit_tests/proc/start_game() if (SSticker.current_state == GAME_STATE_PREGAME) SSticker.current_state = GAME_STATE_SETTING_UP - log_unit_test("Round has been started.") + UT.debug("Round has been started.", __FILE__, __LINE__) stage++ else - log_unit_test("Unable to start testing; SSticker.current_state=[SSticker.current_state]!") + UT.fail("Unable to start testing; SSticker.current_state=[SSticker.current_state]!", __FILE__, __LINE__) del world /datum/controller/subsystem/unit_tests/proc/handle_tests() @@ -49,19 +54,22 @@ curr.len-- if (test.map_path && current_map && current_map.path != test.map_path) - test.pass("[ascii_red]Check Disabled: This test is not allowed to run on this map.") + test.pass("[ascii_red]Check Disabled: This test is not allowed to run on this map.", __FILE__, __LINE__) if (MC_TICK_CHECK) return continue if (test.disabled) - test.pass("[ascii_red]Check Disabled: [test.why_disabled]") + test.pass("[ascii_red]Check Disabled: [test.why_disabled]", __FILE__, __LINE__) if (MC_TICK_CHECK) return continue + TEST_GROUP_OPEN("[test.name]") if (test.start_test() == null) // Runtimed. - test.fail("Test Runtimed") + test.fail("Test Runtimed: [test.name]", __FILE__, __LINE__) + TEST_GROUP_CLOSE("[test.name]") + if (test.async) async_tests += test @@ -82,8 +90,10 @@ var/datum/unit_test/test = current_async[current_async.len] current_async.len-- + TEST_GROUP_OPEN("[test.name]") if (test.check_result()) async_tests -= test + TEST_GROUP_CLOSE("[test.name]") if (MC_TICK_CHECK) return @@ -108,9 +118,9 @@ if (4) // Finalization. if(all_unit_tests_passed) - log_unit_test("[ascii_green]**** All Unit Tests Passed \[[total_unit_tests]\] ****[ascii_reset]") + UT.pass("**** All Unit Tests Passed \[[total_unit_tests]\] ****", __FILE__, __LINE__) else - log_unit_test("[ascii_red]**** \[[failed_unit_tests]\\[total_unit_tests]\] Unit Tests Failed ****[ascii_reset]") + UT.fail("**** \[[unit_tests_failures]\] Errors Encountered! Read the logs above! ****", __FILE__, __LINE__) del world #endif diff --git a/code/unit_tests/subsystem_init.dm b/code/unit_tests/subsystem_init.dm new file mode 100644 index 00000000000..1670ccd0752 --- /dev/null +++ b/code/unit_tests/subsystem_init.dm @@ -0,0 +1,17 @@ +/// Tests that all subsystems that need to properly initialize. +/datum/unit_test/subsystem_init + name = "Controller Subsystem Init" + +/datum/unit_test/subsystem_init/start_test() + for(var/datum/controller/subsystem/subsystem as anything in Master.subsystems) + if(subsystem.flags & SS_NO_INIT) + continue + if(!(subsystem.init_state & SS_INITSTATE_DONE)) + var/message = "[subsystem] ([subsystem.type]) is a subsystem meant to initialize but doesn't get set as initialized." + + if (subsystem.flags & SS_OK_TO_FAIL_INIT) + TEST_NOTICE("[src] - [message]\nThis subsystem is marked as SS_OK_TO_FAIL_INIT. This is still a bug, but it is non-blocking.") + else + return TEST_FAIL(message) + + return TEST_PASS("All subsystems initialize correctly.") diff --git a/code/unit_tests/timer_sanity.dm b/code/unit_tests/timer_sanity.dm new file mode 100644 index 00000000000..213e455b7f9 --- /dev/null +++ b/code/unit_tests/timer_sanity.dm @@ -0,0 +1,7 @@ +/datum/unit_test/timer_sanity + name = "SStimer Sanity (no negative count) Test" + +/datum/unit_test/timer_sanity/start_test() + TEST_ASSERT(SStimer.bucket_count >= 0, + "SStimer is going into negative bucket count from something") + return TEST_PASS("SStimer bucket count is positive, as expected.") diff --git a/code/unit_tests/unit_test.dm b/code/unit_tests/unit_test.dm index a6896a441d9..578c7cd366a 100644 --- a/code/unit_tests/unit_test.dm +++ b/code/unit_tests/unit_test.dm @@ -26,7 +26,7 @@ var/all_unit_tests_passed = 1 -var/failed_unit_tests = 0 +var/unit_tests_failures = 0 var/total_unit_tests = 0 // For console out put in Linux/Bash makes the output green or red. @@ -49,18 +49,86 @@ var/ascii_reset = "[ascii_esc]\[0m" var/why_disabled = "No reason set." // If we disable a unit test we will display why so it reminds us to check back on it later. var/map_path // This should be the same as the path var on /datum/map - The unit test will only run for that map -/datum/unit_test/proc/fail(var/message) + +/** + * Log levels used to prettify correctly, only defined in this file (aka undef'd at the end) + * Build unit test messages as per https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions, or for console output + */ +#define LOG_UNIT_TEST_DEBUG 7 +#define LOG_UNIT_TEST_INFORMATION 6 +#define LOG_UNIT_TEST_WARNING 4 +#define LOG_UNIT_TEST_ERROR 3 + + +/datum/unit_test/proc/log_unit_test(var/severity, var/message, var/filename, var/line, var/title) + + #if defined(MANUAL_UNIT_TEST) + + // We are manually running, write in a more sensible format + switch(severity) + if(LOG_UNIT_TEST_DEBUG) + severity = "\[\[ DEBUG \]\] " + if(LOG_UNIT_TEST_INFORMATION) + severity = "[ascii_green] *** NOTICE *** [ascii_reset] " + if(LOG_UNIT_TEST_WARNING) + severity = "[ascii_yellow] === WARNING === [ascii_reset] " + if(LOG_UNIT_TEST_ERROR) + severity = "[ascii_red] !!! FAILURE !!! [ascii_reset] " + #else + + // We are running off Travis, which means github (or someone fucked up very badly) + + // Spaces or lack thereof are significant here! + switch(severity) + if(LOG_UNIT_TEST_DEBUG) + severity = "debug" + if(LOG_UNIT_TEST_INFORMATION) + severity = "notice " + if(LOG_UNIT_TEST_WARNING) + severity = "warning " + if(LOG_UNIT_TEST_ERROR) + severity = "error " + + // Of the #if defined(MANUAL_UNIT_TEST) + #endif + + var/printstring = "::[severity]" + + if(title) + printstring += " title=[title]" + + printstring += "::[message] → " + TEST_OUTPUT_U_CYAN("@@@[filename]:[line]") + + + world.log << printstring + + +/datum/unit_test/proc/debug(var/message, var/file, var/line) + log_unit_test(LOG_UNIT_TEST_DEBUG, message, file, line) + +/datum/unit_test/proc/notice(var/message, var/file, var/line) + log_unit_test(LOG_UNIT_TEST_INFORMATION, message, file, line) + +/datum/unit_test/proc/warn(var/message, var/file, var/line) + log_unit_test(LOG_UNIT_TEST_WARNING, message, file, line) + +/datum/unit_test/proc/fail(var/message, var/file, var/line) all_unit_tests_passed = 0 - failed_unit_tests++ + unit_tests_failures++ reported = 1 - log_unit_test("[ascii_red]!!! FAILURE !!! \[[name]\]: [message][ascii_reset]") + log_unit_test(LOG_UNIT_TEST_ERROR, message, file, line) + return UNIT_TEST_FAILED -/datum/unit_test/proc/pass(var/message) +/datum/unit_test/proc/pass(var/message, var/file, var/line) reported = 1 - log_unit_test("[ascii_green]*** SUCCESS *** \[[name]\]: [message][ascii_reset]") + log_unit_test(LOG_UNIT_TEST_INFORMATION, "[ascii_green][message][ascii_reset]", file, line, title = "SUCCESS: [name]") + return UNIT_TEST_PASSED -/datum/unit_test/proc/warn(var/message) - log_unit_test("[ascii_yellow]=== WARNING === \[[name]\]: [message][ascii_reset]") + +#undef LOG_UNIT_TEST_DEBUG +#undef LOG_UNIT_TEST_INFORMATION +#undef LOG_UNIT_TEST_WARNING +#undef LOG_UNIT_TEST_ERROR /datum/unit_test/proc/start_test() fail("No test proc.") diff --git a/code/unit_tests/vueui_tests.dm b/code/unit_tests/vueui_tests.dm index ab458749615..cd610b2972a 100644 --- a/code/unit_tests/vueui_tests.dm +++ b/code/unit_tests/vueui_tests.dm @@ -10,13 +10,13 @@ var/datum/vueui_var_monitor/VM = new path() if (!VM.var_holders || !VM.var_holders.len) - log_unit_test("[ascii_red]--------------- VueUI var monitor has an empty var_holders list: [VM.type].[ascii_reset]") + TEST_FAIL("VueUI var monitor has an empty var_holders list: [VM.type].") count_failed++ if (count_failed) - fail("\[[count_failed]\] VueUI var monitors without var holders discovered.") + TEST_FAIL("\[[count_failed]\] VueUI var monitors without var holders discovered.") else - pass("All VueUI var monitors have var holders.") + TEST_PASS("All VueUI var monitors have var holders.") return TRUE @@ -35,7 +35,7 @@ try subject = new VM.subject_type() catch () - log_unit_test("[ascii_red]--------------- VueUI var monitor subject runtimed while being spawned. Monitor: [VM.type].[ascii_reset]") + TEST_FAIL("VueUI var monitor subject runtimed while being spawned. Monitor: [VM.type].") count_failed++ continue @@ -43,9 +43,9 @@ count_failed++ if (count_failed) - fail("\[[count_failed]\] VueUI var monitors have invalid var watches.") + TEST_FAIL("\[[count_failed]\] VueUI var monitors have invalid var watches.") else - pass("All VueUI var monitors have valid var watchers.") + TEST_PASS("All VueUI var monitors have valid var watchers.") return TRUE @@ -55,11 +55,11 @@ var/datum/vueui_var_holder/VH = _iter if (!VH.source_key || !VH.data_key) - log_unit_test("[ascii_red]--------------- VueUI var monitor has no source or data key: [VM.type].[ascii_reset]") + TEST_FAIL("VueUI var monitor has no source or data key: [VM.type].") . = FALSE continue if (!(VH.source_key in subject.vars)) - log_unit_test("[ascii_red]--------------- VueUI var monitor is watching a var '[VH.source_key]' not found on the subject: [VM.type]. Subject: [subject.type].[ascii_reset]") + TEST_FAIL("VueUI var monitor is watching a var '[VH.source_key]' not found on the subject: [VM.type]. Subject: [subject.type].") . = FALSE continue diff --git a/code/unit_tests/zas_tests.dm b/code/unit_tests/zas_tests.dm index 33bfc017bd2..6eb6bbf8d01 100644 --- a/code/unit_tests/zas_tests.dm +++ b/code/unit_tests/zas_tests.dm @@ -27,12 +27,12 @@ datum/unit_test/zas_area_test/start_test() var/list/test = test_air_in_area(area_path, expectation) if(isnull(test)) - fail("Check Runtimed") + TEST_FAIL("Check Runtimed") if(test["result"] == SUCCESS) - pass(test["msg"]) + TEST_PASS(test["msg"]) else - fail(test["msg"]) + TEST_FAIL(test["msg"]) return 1 // ================================================================================================== @@ -141,14 +141,14 @@ datum/unit_test/zas_area_test/mining_area /datum/unit_test/zas_supply_shuttle_moved/start_test() if(!SSshuttle) - fail("The shuttle controller is not setup at time of test.") + TEST_FAIL("The shuttle controller is not setup at time of test.") return 1 if(!SSshuttle.shuttles.len) if(length(current_map.map_shuttles)) - fail("This map should have shuttles, but it doesn't!") + TEST_FAIL("This map should have shuttles, but it doesn't!") return 1 else - pass("This map is not supposed to have any shuttles.") + TEST_PASS("This map is not supposed to have any shuttles.") return 1 shuttle = SScargo.shuttle @@ -163,10 +163,10 @@ datum/unit_test/zas_area_test/mining_area /datum/unit_test/zas_supply_shuttle_moved/check_result() if(!shuttle) - pass("This map has no supply shuttle.") + TEST_PASS("This map has no supply shuttle.") return 1 if(shuttle.moving_status == SHUTTLE_IDLE && !shuttle.at_station()) - fail("Shuttle Did not Move") + TEST_FAIL("Shuttle Did not Move") return 1 if(!shuttle.at_station()) @@ -182,47 +182,56 @@ datum/unit_test/zas_area_test/mining_area for(var/area/A in shuttle.shuttle_area) var/list/test = test_air_in_area(A.type) if(isnull(test)) - fail("Check Runtimed") + TEST_FAIL("Check Runtimed") return 1 switch(test["result"]) - if(SUCCESS) pass(test["msg"]) - else fail(test["msg"]) + if(SUCCESS) TEST_PASS(test["msg"]) + else TEST_FAIL(test["msg"]) return 1 /datum/unit_test/zas_active_edges name = "ZAS: Roundstart Active Edges" /datum/unit_test/zas_active_edges/start_test() - if(SSair.active_edges.len) - fail("[SSair.active_edges.len] edges active at round-start!") - else - pass("No active ZAS edges at round-start.") - return TRUE + + // Nothing went wrong (this time...) + if(!(SSair.active_edges.len)) + TEST_PASS("No active ZAS edges at round-start.") + return UNIT_TEST_PASSED + + + /**Something went wrong + * compose a message and fail the test, let the poor soul try to figure out where the issue is, assuming it's not intermittent + */ + var/fail_message = "[SSair.active_edges.len] edges active at round-start!\n" for(var/connection_edge/E in SSair.active_edges) var/connection_edge/unsimulated/U = E if(istype(U)) var/turf/T = U.B if(istype(T)) - log_unit_test("[ascii_red]---- [U.A.name] and [T.name] ([T.x], [T.y], [T.z]) have mismatched gas mixtures![ascii_reset]") + fail_message += "--> [U.A.name] and [T.name] ([T.x], [T.y], [T.z]) have mismatched gas mixtures! <--\n" else - log_unit_test("[ascii_red]----[U.A.name] and [U.B] have mismatched gas mixtures![ascii_reset]") + fail_message += "--> [U.A.name] and [U.B] have mismatched gas mixtures! <--\n" var/zone/A = U.A - var/offending_turfs = "Problem turfs: " + var/offending_turfs = "Problem turfs: \n" for(var/turf/simulated/S in A.contents) if(S.oxygen || S.nitrogen) - offending_turfs += "[S] ([S.x], [S.y], [S.z]); " + offending_turfs += "[S] ([S.x], [S.y], [S.z])\t" + + fail_message += "[offending_turfs]" - log_unit_test("[ascii_red]-------- [offending_turfs][ascii_reset]") else var/connection_edge/zone/Z = E var/zone/problem if(!istype(Z)) return - log_unit_test("[ascii_red]---- [Z.A.name] and [Z.B.name] have mismatched gas mixtures![ascii_reset]") + + fail_message += "--> [Z.A.name] and [Z.B.name] have mismatched gas mixtures! <--\n" + if(Z.A.air.gas.len && Z.B.air.gas.len) - log_unit_test("[ascii_red]-------- Both zones have gas mixtures defined; either one is a normally vacuum zone exposed to a breach, or two differing gases are mixing at round-start.[ascii_reset]") + fail_message += "--> Both zones have gas mixtures defined; either one is a normally vacuum zone exposed to a breach, or two differing gases are mixing at round-start. <--\n" continue else if(Z.A.air.gas.len) problem = Z.A @@ -235,12 +244,12 @@ datum/unit_test/zas_area_test/mining_area var/offending_turfs = "Problem turfs: " for(var/turf/simulated/S in problem.contents) if(S.oxygen || S.nitrogen) - offending_turfs += "[S] ([S.x], [S.y], [S.z]); " + offending_turfs += "[S] ([S.x], [S.y], [S.z])\t" - log_unit_test("[ascii_red]-------- [offending_turfs][ascii_reset]") + fail_message += "[offending_turfs]" - - return FALSE + TEST_FAIL("[fail_message]") + return UNIT_TEST_FAILED #undef UT_NORMAL #undef UT_VACUUM diff --git a/code/world.dm b/code/world.dm index 501140878e3..477fbe6dc92 100644 --- a/code/world.dm +++ b/code/world.dm @@ -86,7 +86,16 @@ var/global/datum/global_init/init = new () . = ..() #ifdef UNIT_TEST - log_unit_test("Unit Tests Enabled. This will destroy the world when testing is complete.") + #if defined(MANUAL_UNIT_TEST) + + world.log << "[ascii_green] *** NOTICE *** [ascii_reset] Unit Tests Enabled. This will destroy the world when testing is complete." + + #else + + world.log << "::notice::Unit Tests Enabled. This will destroy the world when testing is complete." + + #endif + load_unit_test_changes() #endif diff --git a/html/changelogs/fluffyghost-unittestsrework.yml b/html/changelogs/fluffyghost-unittestsrework.yml new file mode 100644 index 00000000000..9810733b1df --- /dev/null +++ b/html/changelogs/fluffyghost-unittestsrework.yml @@ -0,0 +1,48 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: Fluffyghost + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - refactor: "Refactored the various unit tests to use macros to indicate pass/fail and logging." + - backend: "Added a new unit tests logging system that integrates with github output, prettifying it, and is able to be switched back to terminal-reading mode with a flick of a commented header/define." + - rscadd: "Added a manual unit tests define file, for when you want to run the unit tests locally, with logging to terminal output readability." + - rscadd: "Added some assertion macros to be used for unit tests, to make their writing less annoying." + - rscadd: "All unit tests logging now report from what file and line they come from, instead of having you guess and search around for where they originate." + - rscadd: "Added a create and destroy unit test, it verifies that all items can be created and destroyed without issues, do not sleep during, runtime, error out, are not set as initialized, and returns an hint during initialization, and do not harddel at their destroy, at least in their default state." + - rscadd: "Added a subsystem initialization unit test, that verify all subsystems initialize correctly." + - rscadd: "Added an SStimer unit test, that verify the bucket does not go negative."