mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-06 06:52:39 +00:00
## About The Pull Request This PR adds a new unit test for all organs, a new unit test for lungs, and includes improvements for the existing breath and organ_set_bonus tests. Using the tests, I was able to root out bugs in the organs. This PR includes an advanced refactor of several developer-facing functions. This PR certainly represents a "quality pass" for organs which will make them easier to develop from now on. ### Synopsis of changes: 1. Fixed many fundamental bugs in organ code, especially in `Insert()`/`Remove()` and their overrides. 2. Added two new procs to `/obj/item/organ` named `on_insert` and `on_remove`, each being called after `Insert()`/`Remove()`. 3. Added `organ_effects` lazylist to `/obj/item/organ`. Converted `organ_traits` to lazylist. 2x less empty lists per organ. 4. Adding `SHOULD_CALL_PARENT(TRUE)` to `Insert()`/`Remove()` was very beneficial to stability and overall code health. 5. Created unit test `organ_sanity` for all usable organs in the game. Tests insertion and removal. 6. Created unit test `lungs_sanity` for `/obj/item/organ/internal/lungs`. 7. Improved `breath_sanity` unit tests with additional tests and conditions. 8. Improved `organ_set_bonus_sanity` unit tests with better documentation and maintainable code. --- ### Granular bug/fix list: - A lot of organs are overriding `Insert()` to apply unique side-effects, but aren't checking the return value of the parent proc which causes the activation of side-effects even if the insertion technically fails. I noticed the use-case of applying "unique side-effects" is repeated across a lot of organs in the game, and by overriding `Insert()` the potential for bugs is very high; I solved this problem with inversion-of-control by adding two new procs to `/obj/item/organ` named `on_insert` and `on_remove`, each being called after `Insert()` and `Remove()` succeed. - Many organs, such as abductor "glands", cursed heart, demon heart, alien hive-node, alien plasma-vessel, etc, were not returning their parent's `Insert()` proc return value at all, and as a result those organs `Insert()`s were always returning `null`. I have been mopping those bugs up in my last few PRs, and now the unit test reveals it all. Functions such as those in surgery expect a truthy value to be returned from `Insert()` to represent insertion success, and otherwise it force-moves the organ out of the mob. - Fixed abductor "glands" which had a hard-del bug due to their `Remove()` not calling the parent proc. - Fixed cybernetic arm implants which had a hard-del bug due to `Remove()` not resetting their `hand` variable to `null`. - Fixed lungs gas exchange implementation, which was allowing exhaled gases to feedback into the inhaled gases, which caused Humans to inhale much more gas than intended and not exhale expected gases. ### Overview of the `organ_sanity` unit test: - The new `organ_sanity` unit test gathers all "usable" organs in the game and tests to see if their `Insert()` and `Remove()` functions behave as we expect them to. - Some organs, such as the Nightmare Brain, cause the mob's species to change which subsequently swaps out all of their organs; the unit test accounts for these organs via the typecache `species_changing_organs`. - Some organs are not usable in-game and can't be unit tested, so the unit test accounts for them via the typecache `test_organ_blacklist`. ### Overview of the `lungs_sanity` unit test: - This unit test focuses on `/obj/item/organ/internal/lungs` including Plasmaman and Ashwalker lungs. The test focuses on testing the lungs' `check_breath()` proc. - The tests are composed of calling `check_breath` with different gas mixes to test breathing and suffocation. - Includes gas exchange test for inhaled/exhaled gases, such as O2 to CO2. ### Improvements to the `breath_sanity` unit tests: - Added additional tests for suffocation with empty internals, pure Nitrogen internals, and a gas-less turf. - Includes slightly more reliable tests for internals tanks. ## Why It's Good For The Game **Organs and Lungs were mostly untested. Too many refactors have been submitted without the addition of unit tests to prove the code works at all.** Time to stop. _Time to get some help_. Due to how bad the code health is in organs, any time we've tried to work with them some sort of bug caused them to blow up in our faces. I am trying to fix some of that by establishing some standard testing for organs. These tests have revealed and allowed me to fix lot of basic developer errors/oversights, as well as a few severe bugs.  ## Changelog 🆑 A.C.M.O. fix: Fixed lungs gas exchange implementation, so you always inhale and exhale the correct gases. fix: Fixed a large quantity of hard-deletes which were being caused by organs and cybernetic organs. fix: Fixed many organs which were applying side-effects regardless of whether or not the insertion failed. code: Added unit tests for Organs. code: Added unit tests for Lungs. code: Improved unit tests for breathing. code: Improved unit tests for DNA Infuser organs. /🆑
237 lines
8.1 KiB
Plaintext
237 lines
8.1 KiB
Plaintext
//include unit test files in this module in this ifdef
|
|
//Keep this sorted alphabetically
|
|
|
|
#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
|
|
|
|
/// For advanced cases, fail unconditionally but don't return (so a test can return multiple results)
|
|
#define TEST_FAIL(reason) (Fail(reason || "No reason", __FILE__, __LINE__))
|
|
|
|
/// Asserts that a condition is true
|
|
/// If the condition is not true, fails the test
|
|
#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, fails otherwise
|
|
/// 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, fails otherwise
|
|
/// 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)
|
|
|
|
/// *Only* run the test provided within the parentheses
|
|
/// This is useful for debugging when you want to reduce noise, but should never be pushed
|
|
/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)`
|
|
#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; }
|
|
|
|
/// Logs a noticable message on GitHub, but will not mark as an error.
|
|
/// Use this when something shouldn't happen and is of note, but shouldn't block CI.
|
|
/// Does not mark the test as failed.
|
|
#define TEST_NOTICE(source, message) source.log_for_test((##message), "notice", __FILE__, __LINE__)
|
|
|
|
/// Constants indicating unit test completion status
|
|
#define UNIT_TEST_PASSED 0
|
|
#define UNIT_TEST_FAILED 1
|
|
#define UNIT_TEST_SKIPPED 2
|
|
|
|
#define TEST_PRE 0
|
|
#define TEST_DEFAULT 1
|
|
/// After most test steps, used for tests that run long so shorter issues can be noticed faster
|
|
#define TEST_LONGER 10
|
|
/// This must be the last test to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time.
|
|
#define TEST_CREATE_AND_DESTROY INFINITY
|
|
|
|
/// Change color to red on ANSI terminal output, if enabled with -DANSICOLORS.
|
|
#ifdef ANSICOLORS
|
|
#define TEST_OUTPUT_RED(text) "\x1B\x5B1;31m[text]\x1B\x5B0m"
|
|
#else
|
|
#define TEST_OUTPUT_RED(text) (text)
|
|
#endif
|
|
/// Change color to green on ANSI terminal output, if enabled with -DANSICOLORS.
|
|
#ifdef ANSICOLORS
|
|
#define TEST_OUTPUT_GREEN(text) "\x1B\x5B1;32m[text]\x1B\x5B0m"
|
|
#else
|
|
#define TEST_OUTPUT_GREEN(text) (text)
|
|
#endif
|
|
|
|
/// A trait source when adding traits through unit tests
|
|
#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
|
|
|
|
#include "ablative_hud.dm"
|
|
#include "achievements.dm"
|
|
#include "anchored_mobs.dm"
|
|
#include "anonymous_themes.dm"
|
|
#include "antag_moodlets.dm"
|
|
#include "area_contents.dm"
|
|
#include "armor_verification.dm"
|
|
#include "autowiki.dm"
|
|
#include "barsigns.dm"
|
|
#include "baseturfs.dm"
|
|
#include "bespoke_id.dm"
|
|
#include "binary_insert.dm"
|
|
#include "blindness.dm"
|
|
#include "bloody_footprints.dm"
|
|
#include "breath.dm"
|
|
#include "cable_powernets.dm"
|
|
#include "card_mismatch.dm"
|
|
#include "cardboard_cutouts.dm"
|
|
#include "chain_pull_through_space.dm"
|
|
#include "chat_filter.dm"
|
|
#include "circuit_component_category.dm"
|
|
#include "closets.dm"
|
|
#include "combat.dm"
|
|
#include "component_tests.dm"
|
|
#include "confusion.dm"
|
|
#include "connect_loc.dm"
|
|
#include "container_sanity.dm"
|
|
#include "crayons.dm"
|
|
#include "create_and_destroy.dm"
|
|
#include "dcs_get_id_from_elements.dm"
|
|
#include "designs.dm"
|
|
#include "door_access.dm"
|
|
#include "dragon_expiration.dm"
|
|
#include "drink_icons.dm"
|
|
#include "dummy_spawn.dm"
|
|
#include "dynamic_ruleset_sanity.dm"
|
|
#include "egg_glands.dm"
|
|
#include "emoting.dm"
|
|
#include "focus_only_tests.dm"
|
|
#include "food_edibility_check.dm"
|
|
#include "full_heal.dm"
|
|
#include "gas_transfer.dm"
|
|
#include "get_turf_pixel.dm"
|
|
#include "geyser.dm"
|
|
#include "greyscale_config.dm"
|
|
#include "hallucination_icons.dm"
|
|
#include "heretic_knowledge.dm"
|
|
#include "heretic_rituals.dm"
|
|
#include "holidays.dm"
|
|
#include "human_through_recycler.dm"
|
|
#include "hunger_curse.dm"
|
|
#include "hydroponics_extractor_storage.dm"
|
|
#include "hydroponics_harvest.dm"
|
|
#include "hydroponics_self_mutations.dm"
|
|
#include "hydroponics_validate_genes.dm"
|
|
#include "inhands.dm"
|
|
#include "json_savefile_importing.dm"
|
|
#include "keybinding_init.dm"
|
|
#include "knockoff_component.dm"
|
|
#include "lesserform.dm"
|
|
#include "limbsanity.dm"
|
|
#include "lungs.dm"
|
|
#include "load_map_security.dm"
|
|
#include "machine_disassembly.dm"
|
|
#include "mapload_space_verification.dm"
|
|
#include "mapping.dm"
|
|
#include "mecha_damage.dm"
|
|
#include "medical_wounds.dm"
|
|
#include "merge_type.dm"
|
|
#include "metabolizing.dm"
|
|
#include "mindbound_actions.dm"
|
|
#include "missing_icons.dm"
|
|
#include "mob_faction.dm"
|
|
#include "mob_spawn.dm"
|
|
#include "modsuit.dm"
|
|
#include "modular_map_loader.dm"
|
|
#include "monkey_business.dm"
|
|
#include "mouse_bite_cable.dm"
|
|
#include "mutant_hands_consistency.dm"
|
|
#include "mutant_organs.dm"
|
|
#include "novaflower_burn.dm"
|
|
#include "ntnetwork_tests.dm"
|
|
#include "nuke_cinematic.dm"
|
|
#include "objectives.dm"
|
|
#include "operating_table.dm"
|
|
#include "orderable_items.dm"
|
|
#include "organs.dm"
|
|
#include "organ_set_bonus.dm"
|
|
#include "outfit_sanity.dm"
|
|
#include "paintings.dm"
|
|
#include "pills.dm"
|
|
#include "plane_double_transform.dm"
|
|
#include "plane_dupe_detector.dm"
|
|
#include "plantgrowth_tests.dm"
|
|
#include "preference_species.dm"
|
|
#include "preferences.dm"
|
|
#include "projectiles.dm"
|
|
#include "quirks.dm"
|
|
#include "range_return.dm"
|
|
#include "rcd.dm"
|
|
#include "reagent_id_typos.dm"
|
|
#include "reagent_mob_expose.dm"
|
|
#include "reagent_mod_procs.dm"
|
|
#include "reagent_names.dm"
|
|
#include "reagent_recipe_collisions.dm"
|
|
#include "reagent_transfer.dm"
|
|
#include "resist.dm"
|
|
#include "say.dm"
|
|
#include "screenshot_antag_icons.dm"
|
|
#include "screenshot_basic.dm"
|
|
#include "screenshot_dynamic_human_icons.dm"
|
|
#include "screenshot_humanoids.dm"
|
|
#include "screenshot_husk.dm"
|
|
#include "screenshot_saturnx.dm"
|
|
#include "security_officer_distribution.dm"
|
|
#include "security_levels.dm"
|
|
#include "serving_tray.dm"
|
|
#include "simple_animal_freeze.dm"
|
|
#include "siunit.dm"
|
|
#include "slips.dm"
|
|
#include "spawn_humans.dm"
|
|
#include "spawn_mobs.dm"
|
|
#include "species_change_clothing.dm"
|
|
#include "species_change_organs.dm"
|
|
#include "species_config_sanity.dm"
|
|
#include "species_unique_id.dm"
|
|
#include "species_whitelists.dm"
|
|
#include "spell_invocations.dm"
|
|
#include "spell_mindswap.dm"
|
|
#include "spell_names.dm"
|
|
#include "spell_shapeshift.dm"
|
|
#include "spritesheets.dm"
|
|
#include "stack_singular_name.dm"
|
|
#include "station_trait_tests.dm"
|
|
#include "stomach.dm"
|
|
#include "strange_reagent.dm"
|
|
#include "strippable.dm"
|
|
#include "subsystem_init.dm"
|
|
#include "suit_storage_icons.dm"
|
|
#include "surgeries.dm"
|
|
#include "teleporters.dm"
|
|
#include "tgui_create_message.dm"
|
|
#include "timer_sanity.dm"
|
|
#include "trait_addition_and_removal.dm"
|
|
#include "traitor.dm"
|
|
#include "tutorial_sanity.dm"
|
|
#include "unit_test.dm"
|
|
#include "verify_config_tags.dm"
|
|
#include "verify_emoji_names.dm"
|
|
#include "wizard_loadout.dm"
|
|
#include "worn_icons.dm"
|
|
#ifdef REFERENCE_TRACKING_DEBUG //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
|
|
#include "find_reference_sanity.dm"
|
|
#endif
|
|
|
|
#undef TEST_ASSERT
|
|
#undef TEST_ASSERT_EQUAL
|
|
#undef TEST_ASSERT_NOTEQUAL
|
|
//#undef TEST_FOCUS - This define is used by vscode unit test extension to pick specific unit tests to run and appended later so needs to be used out of scope here
|
|
#endif
|