mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-27 15:01:04 +01:00
Fixes a helper proc that, among other things, had quietly broken all our area building unit tests. Fixes every single goddamn area that had been mapped wrong because the unit tests weren't running. changes: - code_imp: "Updates map_path var for mapping unit tests to be a list instead of a var, allowing for opting-in of unit tests on by-map basis." - code_imp: "Fire alarm directionality map test updated for (almost) all directional wall-mounted objects, with only sccv_horizon map currently opted-in." - bugfix: "Adds extra safety for GLOB.SortedAreas having entries removed but not later re-generated." - bugfix: "Fixes all the incorrectly mapped areas on the Horizon exposed by newly-run unit tests." - imageadd: "Adds currently unused map_helper sprites for airlocks- prep for future work."
275 lines
8.0 KiB
Plaintext
275 lines
8.0 KiB
Plaintext
// This file exists so unit tests work with SMC.
|
|
// 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
|
|
|
|
/*
|
|
The Unit Tests Configuration subsystem
|
|
*/
|
|
|
|
SUBSYSTEM_DEF(unit_tests_config)
|
|
name = "Unit Test Config"
|
|
init_order = INIT_ORDER_PERSISTENT_CONFIGURATION
|
|
flags = SS_NO_FIRE
|
|
|
|
var/datum/unit_test/UT // Logging/output, use this to log things from outside where a specific unit_test is defined
|
|
|
|
///What is our identifier, what pod are we, and hence what are we supposed to run
|
|
var/identifier = null
|
|
|
|
///The configuration, decoded from `config/unit_test/ut_pods_configuration.json`, specific for our identifier
|
|
var/list/config = list()
|
|
|
|
///Boolean, if the tests should fast fail (Anything fails = the pod shuts down)
|
|
var/fail_fast = FALSE
|
|
|
|
///How many times can the pod retries before the unit test is considered failed
|
|
var/retries = 0
|
|
|
|
/datum/controller/subsystem/unit_tests_config/Initialize()
|
|
UT = new
|
|
|
|
//Acquire our identifier, or enter Hopper mode if failing to do so
|
|
try
|
|
src.identifier = rustg_file_read("config/unit_test/identifier.txt")
|
|
|
|
if(isnull(src.identifier))
|
|
UT.fail("**** This UT is being run without an identifier! Aborting... ****")
|
|
del world
|
|
catch()
|
|
UT.fail("**** Exception encountered while trying to acquire an identifier for this UT! ***")
|
|
del world
|
|
|
|
|
|
|
|
ASSERT(!isnull(src.identifier))
|
|
|
|
//Try to acquire our configuration
|
|
try
|
|
|
|
src.config = json_decode(rustg_file_read("config/unit_test/ut_pods_configuration.json"))
|
|
|
|
UT.debug("Pods configuration file read as: [json_encode(src.config)]")
|
|
UT.debug("Will extract the pod configuration for pod with identifier: [src.identifier]")
|
|
|
|
for(var/k in src.config)
|
|
UT.debug("The following pod configuration is defined: [k]")
|
|
|
|
src.config = src.config[src.identifier]
|
|
|
|
UT.debug("Pods configuration extrapolated as: [json_encode(src.config)]")
|
|
|
|
if(isnull(src.config))
|
|
UT.fail("**** This UT is being run without a config, it's null! Aborting... ****")
|
|
del world
|
|
|
|
if(!src.config.len)
|
|
UT.fail("**** This UT is being run without a config! Aborting... ****")
|
|
del world
|
|
|
|
catch(var/exception/e)
|
|
UT.fail("**** Exception encountered while trying to acquire the config for this UT! Identifier: [identifier] ***")
|
|
UT.fail("**** Exception encountered: [e] ***")
|
|
. = ..(e)
|
|
del world
|
|
|
|
refresh_retries(FALSE)
|
|
refresh_fail_fast()
|
|
|
|
return SS_INIT_SUCCESS
|
|
|
|
|
|
/**
|
|
* Refresh the `retries` variable from the environment variables
|
|
*
|
|
* * decrement - A boolean, if `TRUE` it decrements the environment variable that holds the retries left
|
|
*/
|
|
/datum/controller/subsystem/unit_tests_config/proc/refresh_retries(decrement = FALSE)
|
|
src.retries = text2num(world.GetConfig("env", "CI_MAX_RETRIES"))
|
|
|
|
if(decrement && src.retries)
|
|
world.SetConfig("env", "CI_MAX_RETRIES", num2text((src.retries - 1)))
|
|
|
|
/**
|
|
* Refresh the `fail_fast` variable depending on the CI trigger reason
|
|
*/
|
|
/datum/controller/subsystem/unit_tests_config/proc/refresh_fail_fast()
|
|
|
|
//Off by default, so only need to flip it on when we wish it to
|
|
if(world.GetConfig("env", "CI_TRIGGER_REASON") == "merge_group")
|
|
src.fail_fast = TRUE
|
|
|
|
|
|
/*
|
|
The Unit Tests subsystem
|
|
*/
|
|
SUBSYSTEM_DEF(unit_tests)
|
|
name = "Unit Tests"
|
|
init_order = -1e6 // last.
|
|
var/list/queue = list()
|
|
var/list/async_tests = list()
|
|
var/list/current_async
|
|
var/stage = 0
|
|
wait = 2 SECONDS
|
|
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY | RUNLEVEL_INIT
|
|
|
|
|
|
/datum/controller/subsystem/unit_tests/Initialize(timeofday)
|
|
SSunit_tests_config.UT.notice("Initializing Unit Testing", __FILE__, __LINE__)
|
|
|
|
//
|
|
//Start the Round.
|
|
//
|
|
|
|
for(var/thing in subtypesof(/datum/unit_test) - typecacheof(SSatlas.current_map.excluded_test_types))
|
|
var/datum/unit_test/D = new thing
|
|
|
|
if(findtext(D.name, "template") || is_abstract(D))
|
|
qdel(D)
|
|
continue
|
|
|
|
if(!length(D.groups))
|
|
SSunit_tests_config.UT.fail("**** Unit Test has no group assigned! [D.name] ****")
|
|
del world
|
|
|
|
for(var/group in D.groups)
|
|
if((group in SSunit_tests_config.config["unit_test_groups"]) || (SSunit_tests_config.config["unit_test_groups"][1] == "*"))
|
|
BINARY_INSERT_PROC_COMPARE(D, queue, /datum/unit_test, D, compare_priority, COMPARE_KEY)
|
|
break
|
|
|
|
SSunit_tests_config.UT.notice("[queue.len] unit tests loaded.", __FILE__, __LINE__)
|
|
|
|
return SS_INIT_SUCCESS
|
|
|
|
|
|
/datum/controller/subsystem/unit_tests/proc/start_game()
|
|
if (SSticker.current_state == GAME_STATE_PREGAME)
|
|
SSticker.current_state = GAME_STATE_SETTING_UP
|
|
|
|
SSunit_tests_config.UT.debug("Round has been started.", __FILE__, __LINE__)
|
|
stage++
|
|
else
|
|
SSunit_tests_config.UT.fail("Unable to start testing; SSticker.current_state=[SSticker.current_state]!", __FILE__, __LINE__)
|
|
del world
|
|
|
|
/datum/controller/subsystem/unit_tests/proc/handle_tests()
|
|
var/list/curr = queue
|
|
while (curr.len)
|
|
var/datum/unit_test/test = curr[curr.len]
|
|
curr.len--
|
|
|
|
TEST_GROUP_OPEN("[test.name]")
|
|
|
|
if (length(test.map_path) && SSatlas.current_map && !(SSatlas.current_map.path in test.map_path))
|
|
test.pass(TEST_OUTPUT_GREEN("Check Disabled: This test is not allowed to run on this map."), __FILE__, __LINE__)
|
|
TEST_GROUP_CLOSE("[test.name]")
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
continue
|
|
|
|
if (test.disabled)
|
|
test.pass(TEST_OUTPUT_RED("Check Disabled: [test.why_disabled]"), __FILE__, __LINE__)
|
|
TEST_GROUP_CLOSE("[test.name]")
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
continue
|
|
|
|
var/current_test_result = null
|
|
|
|
current_test_result = test.start_test()
|
|
|
|
//If the result is still null, the test have runtimed or not returned a valid result, either way rise an error
|
|
if (isnull(current_test_result))
|
|
test.fail("Unit Test runtimed or returned an illicit result: [test.name]", __FILE__, __LINE__)
|
|
|
|
TEST_GROUP_CLOSE("[test.name]")
|
|
|
|
if (test.async)
|
|
async_tests += test
|
|
|
|
GLOB.total_unit_tests++
|
|
|
|
if(GLOB.unit_tests_failures && SSunit_tests_config.fail_fast)
|
|
SSunit_tests_config.UT.fail("**** Fail fast is enabled and an unit test failed! Aborting... ****", __FILE__, __LINE__)
|
|
handle_tests_ending(TRUE)
|
|
break
|
|
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
|
|
if (!curr.len)
|
|
stage++
|
|
|
|
/datum/controller/subsystem/unit_tests/proc/handle_async(resumed = 0)
|
|
if (!resumed)
|
|
current_async = async_tests.Copy()
|
|
|
|
var/list/async = current_async
|
|
while (async.len)
|
|
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
|
|
|
|
if (!async.len)
|
|
stage++
|
|
|
|
/datum/controller/subsystem/unit_tests/fire(resumed = 0)
|
|
switch (stage)
|
|
if (0)
|
|
start_game()
|
|
|
|
if (1)
|
|
// wait a moment
|
|
stage++
|
|
|
|
if (2) // do normal tests
|
|
handle_tests()
|
|
|
|
if (3)
|
|
handle_async(resumed)
|
|
|
|
if (4) // Finalization.
|
|
if(GLOB.all_unit_tests_passed)
|
|
SSunit_tests_config.UT.pass("**** All Unit Tests Passed \[[GLOB.total_unit_tests]\] ****", __FILE__, __LINE__)
|
|
handle_tests_ending(FALSE)
|
|
else
|
|
SSunit_tests_config.UT.fail("**** \[[GLOB.unit_tests_failures]\] Errors Encountered! Read the logs above! ****", __FILE__, __LINE__)
|
|
handle_tests_ending(TRUE)
|
|
|
|
/datum/controller/subsystem/unit_tests/proc/handle_tests_ending(is_failure = FALSE)
|
|
if(is_failure && SSunit_tests_config.retries)
|
|
SSunit_tests_config.refresh_retries(TRUE)
|
|
world.Reboot("Restarting for another UT try, remaining tries: [SSunit_tests_config.retries]", TRUE)
|
|
else
|
|
del world
|
|
|
|
//This is only valid during unit tests
|
|
/world/Error(var/exception/e)
|
|
CAN_BE_REDEFINED(TRUE)
|
|
var/datum/unit_test/UT
|
|
|
|
//Try to use the SSunit_tests_config.UT, but if for some god forsaken reason it doesn't exist, make a new one
|
|
if(SSunit_tests_config?.UT)
|
|
UT = SSunit_tests_config?.UT
|
|
else
|
|
UT = new
|
|
|
|
UT.fail("**** !!! Encountered a world exception during unit testing !!! - Exception name: [e.name] → @@@ [e.file]:[e.line] ****", __FILE__, __LINE__)
|
|
|
|
return ..(e)
|
|
|
|
#endif
|