Better Unit Tests - Runs All Of Them On A Single Map (exc. map unit tests) and C&D Split-Up (#96368)

## About The Pull Request

### Don't run every single test

I've had this on my mind for several months and it being brought up
recently reminded me so here we are.

Unit tests only run on `runtimestation_minimal.dmm` now with the
exception of map tests. The following unit tests are declared as map
tests but I didn't see any map dependent logic so 🤷
- `/datum/unit_test/maptest_baseturfs_unmodified_scrape`
- `/datum/unit_test/maptest_baseturfs_placed_on_top`
- `/datum/unit_test/maptest_baseturfs_placed_on_bottom`
- `/datum/unit_test/maptest_get_turf_pixel`
- `/datum/unit_test/maptest_load_map_security`
- `/datum/unit_test/maptest_modular_map_loader`
- `/datum/unit_test/maptest_turf_icons`

`/datum/unit_test/subsystem_init` isn't really a mapping unit test but I
figured somehow, someway, maps might fuck with subsystem initializations
so I just decided to include it.

### Splits up the create & destroy test

Idk, pretty simple. Create & destroy is now split up across all
integration tests and ran in parallel.

## Why It's Good For The Game

oranges promised me "500 nzd" yo

## Changelog

No player facing changes
This commit is contained in:
mrmanlikesbt
2026-06-09 06:00:03 +02:00
committed by GitHub
parent c6b96b0675
commit b08ec39e1b
25 changed files with 167 additions and 52 deletions
+11 -1
View File
@@ -39,7 +39,7 @@
/// *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; }
#define TEST_FOCUS(test_path) ##test_path { test_flags = UNIT_TEST_FOCUS; }
/// Run the test provided within the parentheses run_count times
/// Useful for debugging flaky tests that only fail sometimes
@@ -67,6 +67,16 @@
*/
#define TEST_AFTER_CREATE_AND_DESTROY INFINITY
// Unit test bitflags
/// If any unit test has this bitflag, only unit tests with UNIT_TEST_FOCUS will run.
#define UNIT_TEST_FOCUS (1<<0)
/// This unit test only runs on specially designated unit test maps (Should only ever be one).
#define UNIT_TEST_DEBUG_MAP_ONLY (1<<1)
#define UNIT_TEST_BASIC (UNIT_TEST_DEBUG_MAP_ONLY)
#define UNIT_TEST_MAP_TEST (NONE)
/// 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"
+1
View File
@@ -1,6 +1,7 @@
/// Verifies that an area's perception of their "turfs" is correct, and no other area overlaps with them
/// Quite slow, but needed
/datum/unit_test/maptest_area_contents
test_flags = UNIT_TEST_MAP_TEST
priority = TEST_LONGER
/datum/unit_test/maptest_area_contents/Run()
@@ -2,6 +2,7 @@
* This test checks that all areas are connected to their distribution loops
*/
/datum/unit_test/atmospherics_sanity
test_flags = UNIT_TEST_MAP_TEST
priority = TEST_LONGER // we iterate over all atmospherics devices on the starting networks
/// List of areas to start crawling from
@@ -1,5 +1,6 @@
///Checking all powernets to see if they are properly connected and powered.
/datum/unit_test/cable_powernets
test_flags = UNIT_TEST_MAP_TEST
/datum/unit_test/cable_powernets/Run()
for(var/datum/powernet/powernets as anything in SSmachines.powernets)
@@ -1,4 +1,5 @@
/datum/unit_test/cargo_dep_order_locations
test_flags = UNIT_TEST_MAP_TEST
/datum/unit_test/cargo_dep_order_locations/Run()
for(var/datum/job_department/department as anything in SSjob.joinable_departments)
+29 -1
View File
@@ -1,5 +1,7 @@
///Delete one of every type, sleep a while, then check to see if anything has gone fucky
/datum/unit_test/create_and_destroy
// Since this unit test takes so damn long, we split it up across all runners
test_flags = parent_type::test_flags & ~UNIT_TEST_DEBUG_MAP_ONLY
//You absolutely must run after (almost) everything else
priority = TEST_CREATE_AND_DESTROY
@@ -15,6 +17,30 @@ GLOBAL_VAR_INIT(running_create_and_destroy, FALSE)
GLOB.running_create_and_destroy = TRUE
var/list/type_paths_to_check = (valid_typesof(/atom/movable) + valid_typesof(/turf)) - uncreatables // No areas please
// This code is responsible for splitting up create & destroy across multiple integration tests.
var/total_amount_to_check = length(type_paths_to_check)
var/runner_count = length(config.maplist)
var/split_up_amount = floor(total_amount_to_check / runner_count)
var/what_map_index_are_we = 1
for(var/map_name, _map_config in config.maplist)
var/datum/map_config/map_config = _map_config
if(SSmapping.current_map.map_name == map_config.map_name)
break
what_map_index_are_we++
var/start_index = (what_map_index_are_we - 1) * split_up_amount
// Instead of super trying to make it an equal split, we just give the remainder tests to the final runner
var/end_index = (what_map_index_are_we == runner_count) ? total_amount_to_check : start_index + split_up_amount
// +1 because byond's list.Copy() implementation is weird
type_paths_to_check = type_paths_to_check.Copy(start_index, end_index + 1)
log_world("Running create and destroy on [length(type_paths_to_check)] atoms out of the [total_amount_to_check] total")
log_world("([start_index] [type_paths_to_check[1]]) - ([end_index] [type_paths_to_check[length(type_paths_to_check)]])")
for(var/type_path in type_paths_to_check)
if(ispath(type_path, /turf))
spawn_at.ChangeTurf(type_path)
@@ -28,6 +54,8 @@ GLOBAL_VAR_INIT(running_create_and_destroy, FALSE)
else
var/atom/creation = new type_path(spawn_at)
if(QDELETED(creation))
// Same as below
creation = null
continue
//Go all in
qdel(creation, force = TRUE)
@@ -50,7 +78,7 @@ GLOBAL_VAR_INIT(running_create_and_destroy, FALSE)
var/list/queues_we_care_about = list()
// All of em, I want hard deletes too, since we rely on the debug info from them
for(var/i in 1 to GC_QUEUE_HARDDELETE)
for(var/i in GC_QUEUE_FILTER to GC_QUEUE_HARDDELETE)
queues_we_care_about += i
//Now that we've qdel'd everything, let's sleep until the gc has processed all the shit we care about
@@ -25,6 +25,7 @@
* This unit test requires every (unless ignored) atom to have been created at least once
* for a more accurate search, which is why it's run after create_and_destroy is done running.
*/
test_flags = parent_type::test_flags & ~UNIT_TEST_DEBUG_MAP_ONLY
priority = TEST_AFTER_CREATE_AND_DESTROY
/datum/unit_test/dcs_check_list_arguments/Run()
@@ -7,6 +7,7 @@
* Then, checks if every non-ignored region has a fire alarm in it
*/
/datum/unit_test/firedoor_regions
test_flags = UNIT_TEST_MAP_TEST
priority = TEST_LONGER
/datum/unit_test/firedoor_regions/Run()
+1
View File
@@ -1,5 +1,6 @@
/// Tests that [/datum/job/proc/get_default_roundstart_spawn_point] returns a landmark from all joinable jobs.
/datum/unit_test/maptest_job_roundstart_spawnpoints
test_flags = UNIT_TEST_MAP_TEST
/datum/unit_test/maptest_job_roundstart_spawnpoints/Run()
for(var/datum/job/job as anything in SSjob.joinable_occupations)
@@ -1,6 +1,7 @@
/// Verifies that there are no space turfs inside a station area, or on any planetary z-level. Sometimes, these are introduced during the load of the map and are not present in the DMM itself.
/// Let's just make sure that we have a stop-gap measure in place to catch these if they pop up so we don't put it onto production servers should something errant come up.
/datum/unit_test/maptest_mapload_space_verification
test_flags = UNIT_TEST_MAP_TEST
// This test is quite taxing time-wise, so let's run it later than other faster tests.
priority = TEST_LONGER
+1
View File
@@ -1,5 +1,6 @@
/// Conveys all log_mapping messages as unit test failures, as they all indicate mapping problems.
/datum/unit_test/maptest_log_mapping
test_flags = UNIT_TEST_MAP_TEST
// Happen before all other tests, to make sure we only capture normal mapping logs.
priority = TEST_PRE
@@ -1,5 +1,6 @@
///Detects movables that may have been accidentally placed in space, as well as movables which do not have the proper nearspace area (meaning they aren't lit properly.)
/datum/unit_test/maptest_mapping_nearstation_test
test_flags = UNIT_TEST_MAP_TEST
priority = TEST_PRE
/datum/unit_test/maptest_mapping_nearstation_test/Run()
@@ -1,6 +1,7 @@
/// Ensures we do not leave genturfs sitting around post work
/// They serve as notice to the mapper and have no functionality, but it's good to make note of it here
/datum/unit_test/orphaned_genturf
test_flags = UNIT_TEST_MAP_TEST
/datum/unit_test/orphaned_genturf/Run()
for(var/turf/open/genturf/orphaned in ALL_TURFS())
@@ -6,6 +6,7 @@
* - In the type's initialize, REGISTER_REQUIRED_MAP_ITEM() a minimum and maximum
*/
/datum/unit_test/maptest_required_map_items
test_flags = UNIT_TEST_MAP_TEST
/// A list of all typepaths that we expect to be in the required items list
var/list/expected_types = list()
@@ -1,5 +1,6 @@
/// Tests that all subsystems that need to properly initialize.
/datum/unit_test/subsystem_init
test_flags = UNIT_TEST_MAP_TEST
/datum/unit_test/subsystem_init/Run()
for(var/datum/controller/subsystem/subsystem as anything in Master.subsystems)
+50 -27
View File
@@ -27,35 +27,37 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
/proc/focused_tests()
var/list/focused_tests = list()
for (var/datum/unit_test/unit_test as anything in subtypesof(/datum/unit_test))
if (initial(unit_test.focus))
if (unit_test::test_flags & UNIT_TEST_FOCUS)
focused_tests += unit_test
return focused_tests.len > 0 ? focused_tests : null
return length(focused_tests) ? focused_tests : null
/datum/unit_test
/// Do not instantiate if type matches this
abstract_type = /datum/unit_test
//Bit of metadata for the future maybe
var/list/procs_tested
/// The bottom left floor turf of the testing zone
var/turf/run_loc_floor_bottom_left
/// The top right floor turf of the testing zone
var/turf/run_loc_floor_top_right
///The priority of the test, the larger it is the later it fires
/// Behavior flags for this unit test
var/test_flags = UNIT_TEST_BASIC
/// The priority of the test, the larger it is the later it fires
var/priority = TEST_DEFAULT
//internal shit
var/focus = FALSE
var/succeeded = TRUE
var/list/allocated
var/list/fail_reasons
/// How many times this unit test will run. Use the TEST_REPEAT() macro
var/times_to_run = 1
/// List of atoms that we don't want to ever initialize in an agnostic context, like for Create and Destroy. Stored on the base datum for usability in other relevant tests that need this data.
var/static/list/uncreatables = null
// internal shit
/// If this test has passed or not
var/succeeded = TRUE
/// The bottom left floor turf of the testing zone
var/turf/run_loc_floor_bottom_left
/// The top right floor turf of the testing zone
var/turf/run_loc_floor_top_right
/// A list of instances created by this unit test. Use allocate()
var/list/allocated
/// Lazy list of why this unit test failed.
var/list/fail_reasons
/// List of atoms that we don't want to ever initialize in an agnostic context, like for Create and Destroy.
/// Stored on the base datum for usability in other relevant tests that need this data.
var/static/list/uncreatables = null
/// Reference to the blank z-level containing our testing enviroment
var/static/datum/space_level/reservation
/proc/cmp_unit_test_priority(datum/unit_test/a, datum/unit_test/b)
@@ -66,10 +68,9 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
var/datum/map_template/unit_tests/template = new
reservation = template.load_new_z()
if (isnull(uncreatables))
uncreatables = build_list_of_uncreatables()
uncreatables ||= build_list_of_uncreatables()
allocated = new
allocated = list()
run_loc_floor_bottom_left = get_turf(locate(/obj/effect/landmark/unit_test_bottom_left) in GLOB.landmarks_list)
run_loc_floor_top_right = get_turf(locate(/obj/effect/landmark/unit_test_top_right) in GLOB.landmarks_list)
@@ -367,15 +368,37 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
/proc/RunUnitTests()
CHECK_TICK
var/list/tests_to_run = subtypesof(/datum/unit_test)
// Find our primary unit test map & find out if we are the secondary
var/datum/map_config/primary_unit_test_map
var/is_secondary_unit_test_map = FALSE
var/found_secondary_unit_test_map = FALSE
for(var/map_name, _map_config in config.maplist)
var/datum/map_config/map_config = _map_config
if(map_config.is_unit_test_map)
primary_unit_test_map = map_config
if(!LAZYLEN(map_config.skipped_tests) && !found_secondary_unit_test_map)
found_secondary_unit_test_map = TRUE
if(SSmapping.current_map.map_name == map_config.map_name)
is_secondary_unit_test_map = TRUE
var/list/tests_to_run = list()
var/list/focused_tests = list()
for (var/_test_to_run in tests_to_run)
var/datum/unit_test/test_to_run = _test_to_run
if (initial(test_to_run.focus))
focused_tests += test_to_run
for (var/datum/unit_test/potential_test as anything in subtypesof(/datum/unit_test))
// If the test has [UNIT_TEST_DEBUG_MAP_ONLY] and we aren't the primary unit test map, skip it.
// HOWEVER, some unit tests are incompatible with the primary testing map, so we must offload them a secondary one with no blacklisted tests.
if((potential_test::test_flags & UNIT_TEST_DEBUG_MAP_ONLY) && !SSmapping.current_map.is_unit_test_map && \
!(primary_unit_test_map.skipped_tests?.Find(potential_test) && is_secondary_unit_test_map) \
)
continue
if (potential_test::test_flags & UNIT_TEST_FOCUS)
focused_tests += potential_test
continue
tests_to_run += potential_test
if(length(focused_tests))
tests_to_run = focused_tests
primary_unit_test_map = null // I'm paranoid
sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority))
var/list/test_results = list()