code:
#define CHECK_OUTFIT_SLOT(outfit_key, slot_name) if (outfit.##outfit_key) { \
	H.equip_to_slot_or_del(new outfit.##outfit_key(H), ##slot_name, TRUE); \
	/* We don't check the result of equip_to_slot_or_del because it returns false for random jumpsuits, as they delete themselves on init */ \
	if (!H.get_item_by_slot(##slot_name)) { \
		Fail("[outfit.name]'s [#outfit_key] is invalid!"); \
	} \
}

/datum/unit_test/outfit_sanity/Run()
	var/mob/living/carbon/human/H = allocate(/mob/living/carbon/human)

	for (var/outfit_type in subtypesof(/datum/outfit))
		// Only make one human and keep undressing it because it's much faster
		for (var/obj/item/I in H.get_equipped_items(include_pockets = TRUE))
			qdel(I)

		var/datum/outfit/outfit = new outfit_type
		outfit.pre_equip(H, TRUE)

		CHECK_OUTFIT_SLOT(uniform, ITEM_SLOT_ICLOTHING)
		CHECK_OUTFIT_SLOT(suit, ITEM_SLOT_OCLOTHING)
		CHECK_OUTFIT_SLOT(back, ITEM_SLOT_BACK)
		CHECK_OUTFIT_SLOT(belt, ITEM_SLOT_BELT)
		CHECK_OUTFIT_SLOT(gloves, ITEM_SLOT_GLOVES)
		CHECK_OUTFIT_SLOT(shoes, ITEM_SLOT_FEET)
		CHECK_OUTFIT_SLOT(head, ITEM_SLOT_HEAD)
		CHECK_OUTFIT_SLOT(mask, ITEM_SLOT_MASK)
		CHECK_OUTFIT_SLOT(neck, ITEM_SLOT_NECK)
		CHECK_OUTFIT_SLOT(ears, ITEM_SLOT_EARS)
		CHECK_OUTFIT_SLOT(glasses, ITEM_SLOT_EYES)
		CHECK_OUTFIT_SLOT(id, ITEM_SLOT_ID)
		CHECK_OUTFIT_SLOT(suit_store, ITEM_SLOT_SUITSTORE)
		CHECK_OUTFIT_SLOT(l_pocket, ITEM_SLOT_LPOCKET)
		CHECK_OUTFIT_SLOT(r_pocket, ITEM_SLOT_RPOCKET)

		if (outfit.backpack_contents || outfit.box)
			var/list/backpack_contents = outfit.backpack_contents?.Copy()
			if (outfit.box)
				if (!backpack_contents)
					backpack_contents = list()
				backpack_contents.Insert(1, outfit.box)
				backpack_contents[outfit.box] = 1

			for (var/path in backpack_contents)
				var/number = backpack_contents[path] || 1
				for (var/_ in 1 to number)
					if (!H.equip_to_slot_or_del(new path(H), ITEM_SLOT_BACKPACK, TRUE))
						Fail("[outfit.name]'s backpack_contents are invalid! Couldn't add [path] to backpack.")

#undef CHECK_OUTFIT_SLOT


code:
/datum/unit_test/pills/Run()
	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
	var/obj/item/reagent_containers/pill/iron/pill = allocate(/obj/item/reagent_containers/pill/iron)

	TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/iron), FALSE, "Human somehow has iron before taking pill")

	pill.attack(human, human)
	human.Life(SSMOBS_DT)

	TEST_ASSERT(human.has_reagent(/datum/reagent/iron), "Human doesn't have iron after taking pill")

repo_name: AnalWerewolf/sundown

license: mit


code:
// Checks plants for broken tray icons. Use Advanced Proc Call to activate.
// Maybe some day it would be used as unit test.
// -------- IT IS NOW!
/datum/unit_test/plantgrowth/Run()
	var/list/states = icon_states('icons/obj/hydroponics/growing.dmi')
	states |= icon_states('icons/obj/hydroponics/growing_fruits.dmi')
	states |= icon_states('icons/obj/hydroponics/growing_flowers.dmi')
	states |= icon_states('icons/obj/hydroponics/growing_mushrooms.dmi')
	states |= icon_states('icons/obj/hydroponics/growing_vegetables.dmi')
	states |= icon_states('goon/icons/obj/hydroponics.dmi')
	var/list/paths = subtypesof(/obj/item/seeds) - /obj/item/seeds - typesof(/obj/item/seeds/sample) - /obj/item/seeds/lavaland

	for(var/seedpath in paths)
		var/obj/item/seeds/seed = new seedpath

		for(var/i in 1 to seed.growthstages)
			if("[seed.icon_grow][i]" in states)
				continue
			Fail("[seed.name] ([seed.type]) lacks the [seed.icon_grow][i] icon!")

		if(!(seed.icon_dead in states))
			Fail("[seed.name] ([seed.type]) lacks the [seed.icon_dead] icon!")

		if(seed.icon_harvest) // mushrooms have no grown sprites, same for items with no product
			if(!(seed.icon_harvest in states))
				Fail("[seed.name] ([seed.type]) lacks the [seed.icon_harvest] icon!")


code:
/// Requires all preferences to implement required methods.
/datum/unit_test/preferences_implement_everything

/datum/unit_test/preferences_implement_everything/Run()
	var/datum/preferences/preferences = new
	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)

	for (var/preference_type in GLOB.preference_entries)
		var/datum/preference/preference = GLOB.preference_entries[preference_type]
		if (preference.savefile_identifier == PREFERENCE_CHARACTER)
			preference.apply_to_human(human, preference.create_informed_default_value(preferences))

		if (istype(preference, /datum/preference/choiced))
			var/datum/preference/choiced/choiced_preference = preference
			choiced_preference.init_possible_values()

		// Smoke-test is_valid
		preference.is_valid(TRUE)
		preference.is_valid("string")
		preference.is_valid(100)
		preference.is_valid(list(1, 2, 3))

/// Requires all preferences to have a valid, unique savefile_identifier.
/datum/unit_test/preferences_valid_savefile_key

/datum/unit_test/preferences_valid_savefile_key/Run()
	var/list/known_savefile_keys = list()

	for (var/preference_type in GLOB.preference_entries)
		var/datum/preference/preference = GLOB.preference_entries[preference_type]
		if (!istext(preference.savefile_key))
			Fail("[preference_type] has an invalid savefile_key.")

		if (preference.savefile_key in known_savefile_keys)
			Fail("[preference_type] has a non-unique savefile_key `[preference.savefile_key]`!")

		known_savefile_keys += preference.savefile_key

/// Requires all main features have a main_feature_name
/datum/unit_test/preferences_valid_main_feature_name

/datum/unit_test/preferences_valid_main_feature_name/Run()
	for (var/preference_type in GLOB.preference_entries)
		var/datum/preference/choiced/preference = GLOB.preference_entries[preference_type]
		if (!istype(preference))
			continue

		if (preference.category != PREFERENCE_CATEGORY_FEATURES && preference.category != PREFERENCE_CATEGORY_CLOTHING)
			continue

		TEST_ASSERT(!isnull(preference.main_feature_name), "Preference [preference_type] does not have a main_feature_name set!")


code:
/datum/unit_test/projectile_movetypes/Run()
	for(var/path in typesof(/obj/projectile))
		var/obj/projectile/projectile = path
		if(initial(projectile.movement_type) & PHASING)
			Fail("[path] has default movement type PHASING. Piercing projectiles should be done using the projectile piercing system, not movement_types!")

/datum/unit_test/gun_go_bang/Run()
	// test is for a ballistic gun that starts loaded + chambered
	var/obj/item/gun/test_gun = allocate(/obj/item/gun/ballistic/automatic/pistol)
	var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
	var/mob/living/carbon/human/gunner = allocate(/mob/living/carbon/human)
	ADD_TRAIT(victim, TRAIT_PIERCEIMMUNE, INNATE_TRAIT) // So the human isn't randomly affected by shrapnel

	var/obj/item/ammo_casing/loaded_casing = test_gun.chambered
	TEST_ASSERT(loaded_casing, "Gun started without round chambered, should be loaded")
	var/obj/projectile/loaded_bullet = loaded_casing.loaded_projectile
	TEST_ASSERT(loaded_bullet, "Ammo casing has no loaded bullet")

	gunner.put_in_hands(test_gun, forced=TRUE)
	gunner.set_combat_mode(FALSE) // just to make sure we know we're not trying to pistol-whip them
	var/expected_damage = loaded_bullet.damage
	loaded_bullet.def_zone = BODY_ZONE_CHEST
	var/did_we_shoot = test_gun.afterattack(victim, gunner)
	TEST_ASSERT(did_we_shoot, "Gun does not appeared to have successfully fired.")
	TEST_ASSERT_EQUAL(victim.getBruteLoss(), expected_damage, "Victim took incorrect amount of damage, expected [expected_damage], got [victim.getBruteLoss()].")

	var/obj/item/bodypart/expected_part = victim.get_bodypart(BODY_ZONE_CHEST)
	TEST_ASSERT_EQUAL(expected_part.brute_dam, expected_damage, "Intended bodypart took incorrect amount of damage, either it hit another bodypart or armor was incorrectly applied. Expected [expected_damage], got [expected_part.brute_dam].")


code:
/// Ensure every quirk has a unique icon
/datum/unit_test/quirk_icons

/datum/unit_test/quirk_icons/Run()
	var/list/used_icons = list()

	for (var/datum/quirk/quirk_type as anything in subtypesof(/datum/quirk))
		if (initial(quirk_type.abstract_parent_type) == quirk_type)
			continue

		var/icon = initial(quirk_type.icon)

		if (isnull(icon))
			Fail("[quirk_type] has no icon!")
			continue

		if (icon in used_icons)
			Fail("[icon] used in both [quirk_type] and [used_icons[icon]]!")
			continue

		used_icons[icon] = quirk_type


code:
/**
 * Simple unit test to ensure there's no regression in behaviour where machine frames should not be stacked.
 *
 * We attempt to use the RCD to build multiple stacked machine frames on a turf. If we end up with any number that
 * is not equal to 1, this means we've either built no machine frames (bad) or built more than one (regression).
 *
 * If this is successful, we attempt to spawn in some no-density machines that result in machine frames and we run
 * the test again on our turf containing our single frame, deconstructing the machines! This should also not spawn
 * any stacked machine frames.
 */
/datum/unit_test/frame_stacking/Run()
	// First test - RCDs stacking frames.
	var/obj/item/construction/rcd/rcd = allocate(/obj/item/construction/rcd/combat/admin)
	var/mob/living/carbon/human/engineer = allocate(/mob/living/carbon/human)

	engineer.put_in_hands(rcd, forced = TRUE)

	rcd.mode = RCD_MACHINE

	var/list/adjacent_turfs = get_adjacent_open_turfs(engineer)

	if(!length(adjacent_turfs))
		Fail("RCD Test failed - Lack of adjacent open turfs. This may be an issue with the unit test.")

	var/turf/adjacent_turf = adjacent_turfs[1]

	for(var/i in 1 to 10)
		adjacent_turf.rcd_act(engineer, rcd, rcd.mode)

	var/frame_count = 0
	for(var/obj/structure/frame/machine_frame in adjacent_turf.contents)
		frame_count++

	TEST_ASSERT_EQUAL(frame_count, 1, "Expected RCD machine frame stacking test to end up with exactly 1 machine frame.")

	// Second test - Deconstructing stacked machines to stack frames. We'll recycle our old turf to accomplish this.
	for(var/i in 1 to 10)
		// This should be a type path to a machine with no density, that can be wrenched on a turf with another machine of the same type.
		var/obj/machinery/new_machine = new /obj/machinery/recharger(adjacent_turf)
		new_machine.deconstruct(TRUE)

	frame_count = 0
	for(var/obj/structure/frame/machine_frame in adjacent_turf.contents)
		frame_count++

	TEST_ASSERT_EQUAL(frame_count, 1, "Expected no density machine deconstruction frame stacking test to end up with exactly 1 machine frame.")


code:
/datum/unit_test/reagent_id_typos

/datum/unit_test/reagent_id_typos/Run()
	build_chemical_reactions_lists()

	for(var/I in GLOB.chemical_reactions_list_reactant_index)
		for(var/V in GLOB.chemical_reactions_list_reactant_index[I])
			var/datum/chemical_reaction/R = V
			for(var/id in (R.required_reagents + R.required_catalysts))
				if(!GLOB.chemical_reagents_list[id])
					Fail("Unknown chemical id \"[id]\" in recipe [R.type]")


code:
// testing the mob expose procs are working

/datum/reagent/method_patch_test
	name = "method patch test"

/datum/reagent/method_patch_test/expose_mob(mob/living/target, methods = PATCH, reac_volume, show_message = TRUE)
	. = ..()
	if(methods & PATCH)
		target.health = 90
	if(methods & INJECT)
		target.health = 80

/datum/unit_test/reagent_mob_expose/Run()
	// Life() is handled just by tests
	SSmobs.pause()

	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
	var/obj/item/reagent_containers/dropper/dropper = allocate(/obj/item/reagent_containers/dropper)
	var/obj/item/reagent_containers/food/drinks/drink = allocate(/obj/item/reagent_containers/food/drinks/bottle)
	var/obj/item/reagent_containers/pill/patch/patch = allocate(/obj/item/reagent_containers/pill/patch)
	var/obj/item/reagent_containers/syringe/syringe = allocate(/obj/item/reagent_containers/syringe)

	// INGEST
	TEST_ASSERT_EQUAL(human.fire_stacks, 0, "Human has fire stacks before taking phlogiston")
	drink.reagents.add_reagent(/datum/reagent/phlogiston, 10)
	drink.attack(human, human)
	TEST_ASSERT_EQUAL(human.fire_stacks, 1, "Human does not have fire stacks after taking phlogiston")
	human.Life(SSMOBS_DT)
	TEST_ASSERT(human.fire_stacks > 1, "Human fire stacks did not increase after life tick")

	// TOUCH
	dropper.reagents.add_reagent(/datum/reagent/water, 1)
	dropper.afterattack(human, human, TRUE)
	TEST_ASSERT_EQUAL(human.fire_stacks, 0, "Human still has fire stacks after touching water")

	// VAPOR
	TEST_ASSERT_EQUAL(human.drowsyness, 0, "Human is drowsy at the start of testing")
	drink.reagents.clear_reagents()
	drink.reagents.add_reagent(/datum/reagent/nitrous_oxide, 10)
	drink.reagents.trans_to(human, 10, methods = VAPOR)
	TEST_ASSERT_NOTEQUAL(human.drowsyness, 0, "Human is not drowsy after exposure to vapors")

	// PATCH
	human.health = 100
	TEST_ASSERT_EQUAL(human.health, 100, "Human health did not set properly")
	patch.reagents.add_reagent(/datum/reagent/method_patch_test, 1)
	patch.self_delay = 0
	patch.attack(human, human)
	TEST_ASSERT_EQUAL(human.health, 90, "Human health did not update after patch was applied")

	// INJECT
	syringe.reagents.add_reagent(/datum/reagent/method_patch_test, 1)
	syringe.melee_attack_chain(human, human)
	TEST_ASSERT_EQUAL(human.health, 80, "Human health did not update after injection from syringe")

/datum/unit_test/reagent_mob_expose/Destroy()
	SSmobs.ignite()
	return ..()


code:
/datum/unit_test/reagent_mob_procs/Run()
	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
	var/obj/item/food/hotdog/debug/fooditem = allocate(/obj/item/food/hotdog/debug)

	TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human somehow has ketchup before eating")
	TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/medicine/epinephrine), FALSE, "Human somehow has epinephrine before injecting")

	fooditem.attack(human, human)
	human.reagents.add_reagent(/datum/reagent/medicine/epinephrine, 5)

	TEST_ASSERT(human.has_reagent(/datum/reagent/consumable/ketchup), "Human doesn't have ketchup after eating")
	TEST_ASSERT(human.has_reagent(/datum/reagent/medicine/epinephrine), "Human doesn't have epinephrine after injecting")


code:
/datum/unit_test/reagent_recipe_collisions

/datum/unit_test/reagent_recipe_collisions/Run()
	build_chemical_reactions_lists()
	var/list/reactions = list()
	for(var/V in GLOB.chemical_reactions_list_reactant_index)
		reactions += GLOB.chemical_reactions_list_reactant_index[V]
	for(var/i in 1 to (reactions.len-1))
		for(var/i2 in (i+1) to reactions.len)
			var/datum/chemical_reaction/r1 = reactions[i]
			var/datum/chemical_reaction/r2 = reactions[i2]
			if(chem_recipes_do_conflict(r1, r2))
				Fail("Chemical recipe conflict between [r1.type] and [r2.type]")


code:
/// Test that stop, drop, and roll lowers fire stacks
/datum/unit_test/stop_drop_and_roll/Run()
	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)

	TEST_ASSERT_EQUAL(human.fire_stacks, 0, "Human does not have 0 fire stacks pre-ignition")

	human.adjust_fire_stacks(5)
	human.IgniteMob()

	TEST_ASSERT_EQUAL(human.fire_stacks, 5, "Human does not have 5 fire stacks pre-resist")

	// Stop, drop, and roll has a sleep call. This would delay the test, and is not necessary.
	call_async(human, /mob/living/verb/resist)

	TEST_ASSERT(human.fire_stacks < 5, "Human did not lower fire stacks after resisting")

/// Test that you can resist out of a container
/datum/unit_test/container_resist/Run()
	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
	var/obj/structure/closet/closet = allocate(/obj/structure/closet, get_turf(human))

	closet.open(human)
	TEST_ASSERT(!(human in closet.contents), "Human was in the contents of an open closet")

	closet.close(human)
	TEST_ASSERT(human in closet.contents, "Human was not in the contents of the closed closet")

	human.resist()
	TEST_ASSERT(!(human in closet.contents), "Human resisted out of a standard closet, but was still in it")


code:
/// Test to verify message mods are parsed correctly
/datum/unit_test/get_message_mods
	var/mob/host_mob

/datum/unit_test/get_message_mods/Run()
	host_mob = allocate(/mob/living/carbon/human)

	test("Hello", "Hello", list())
	test(";HELP", "HELP", list(MODE_HEADSET = TRUE))
	test(";%Never gonna give you up", "Never gonna give you up", list(MODE_HEADSET = TRUE, MODE_SING = TRUE))
	test(".s Gun plz", "Gun plz", list(RADIO_KEY = RADIO_KEY_SECURITY, RADIO_EXTENSION = RADIO_CHANNEL_SECURITY))
	test("...What", "...What", list())

/datum/unit_test/get_message_mods/proc/test(message, expected_message, list/expected_mods)
	var/list/mods = list()
	TEST_ASSERT_EQUAL(host_mob.get_message_mods(message, mods), expected_message, "Chopped message was not what we expected. Message: [message]")

	for (var/mod_key in mods)
		TEST_ASSERT_EQUAL(mods[mod_key], expected_mods[mod_key], "The value for [mod_key] was not what we expected. Message: [message]")
		expected_mods -= mod_key

	if (expected_mods.len)
		Fail("Some message mods were expected, but were not returned by get_message_mods: [json_encode(expected_mods)]. Message: [message]")


code:
#define SECURITY_OFFICER_DEPARTMENTS list("a", "b", "c", "d")
#define SECURITY_OFFICER_DEPARTMENTS_TO_NAMES (list( \
	"a" = SEC_DEPT_ENGINEERING, \
	"b" = SEC_DEPT_MEDICAL, \
	"c" = SEC_DEPT_SCIENCE, \
	"d" = SEC_DEPT_SUPPLY, \
))

/// Test that security officers with specific distributions get their departments.
/datum/unit_test/security_officer_roundstart_distribution

/datum/unit_test/security_officer_roundstart_distribution/proc/test(
	list/preferences,
	list/expected,
)
	var/list/outcome = get_officer_departments(preferences, SECURITY_OFFICER_DEPARTMENTS)
	var/failure_message = "Tested with [json_encode(preferences)] and expected [json_encode(expected)], got [json_encode(outcome)]"

	if (outcome.len == expected.len)
		for (var/index in 1 to outcome.len)
			if (outcome[index] != expected[index])
				Fail(failure_message)
				return
	else
		Fail(failure_message)

/datum/unit_test/security_officer_roundstart_distribution/Run()
	test_distributions()
	test_with_mock_players()

/datum/unit_test/security_officer_roundstart_distribution/proc/test_distributions()
	test(list("a"), list("a"))
	test(list("a", "b"), list("a", "a"))
	test(list("a", "b", "c"), list("a", "a", "a"))
	test(list("a", "a", "b"), list("a", "a", "a"))
	test(list("a", "a", "b", "b"), list("a", "a", "b", "b"))
	test(list("a", "a", "a", "b"), list("a", "a", "b", "b"))
	test(list("a", "b", "c", "d"), list("a", "b", "b", "a"))
	test(list(SEC_DEPT_NONE), list("d"))
	test(list("a", SEC_DEPT_NONE), list("a", "a"))
	test(list(SEC_DEPT_NONE, SEC_DEPT_NONE, SEC_DEPT_NONE, SEC_DEPT_NONE), list("d", "d", "c", "c"))

/datum/unit_test/security_officer_roundstart_distribution/proc/test_with_mock_players()
	var/mob/dead/new_player/officer_a = create_officer("a")
	var/mob/dead/new_player/officer_b = create_officer("b")
	var/mob/dead/new_player/officer_c = create_officer("c")
	var/mob/dead/new_player/officer_d = create_officer("d")

	var/list/outcome = SSticker.decide_security_officer_departments(
		list(officer_a, officer_b, officer_c, officer_d),
		SECURITY_OFFICER_DEPARTMENTS,
	)

	TEST_ASSERT_EQUAL(outcome[REF(officer_a.new_character)], SECURITY_OFFICER_DEPARTMENTS_TO_NAMES["a"], "Officer A's department outcome was incorrect.")
	TEST_ASSERT_EQUAL(outcome[REF(officer_b.new_character)], SECURITY_OFFICER_DEPARTMENTS_TO_NAMES["b"], "Officer B's department outcome was incorrect.")
	TEST_ASSERT_EQUAL(outcome[REF(officer_c.new_character)], SECURITY_OFFICER_DEPARTMENTS_TO_NAMES["b"], "Officer C's department outcome was incorrect.")
	TEST_ASSERT_EQUAL(outcome[REF(officer_d.new_character)], SECURITY_OFFICER_DEPARTMENTS_TO_NAMES["a"], "Officer D's department outcome was incorrect.")

/datum/unit_test/security_officer_roundstart_distribution/proc/create_officer(preference)
	var/mob/dead/new_player/new_player = allocate(/mob/dead/new_player)
	var/datum/client_interface/mock_client = new

	mock_client.prefs = new
	var/write_success = mock_client.prefs.write_preference(
		GLOB.preference_entries[/datum/preference/choiced/security_department],
		SECURITY_OFFICER_DEPARTMENTS_TO_NAMES[preference],
	)

	TEST_ASSERT(write_success, "Couldn't write department [SECURITY_OFFICER_DEPARTMENTS_TO_NAMES[preference]]")

	var/mob/living/carbon/human/new_character = allocate(/mob/living/carbon/human)
	new_character.mind_initialize()
	new_character.mind.set_assigned_role(SSjob.GetJobType(/datum/job/security_officer))

	new_player.new_character = new_character
	new_player.mock_client = mock_client
	return new_player

/// Test that latejoin security officers are put into the correct department
/datum/unit_test/security_officer_latejoin_distribution

/datum/unit_test/security_officer_latejoin_distribution/proc/test(
	preference,
	list/preferences_of_others,
	expected,
)
	var/list/distribution = list()

	for (var/officer_preference in preferences_of_others)
		var/mob/officer = allocate(/mob/living/carbon/human)
		distribution[officer] = officer_preference

	var/result = get_new_officer_distribution_from_late_join(
		preference,
		SECURITY_OFFICER_DEPARTMENTS,
		distribution,
	)

	var/failure_message = "Latejoin distribution was incorrect (preference = [preference], preferences_of_others = [json_encode(preferences_of_others)])."

	TEST_ASSERT_EQUAL(result, expected, failure_message)

/datum/unit_test/security_officer_latejoin_distribution/Run()
	test("a", list(), "a")
	test("b", list(), "b")
	test("a", list("b"), "b")
	test("a", list("a", "a"), "b")
	test("a", list("a", "a", "b"), "b")
	test("a", list("a", "a", "b", "b"), "c")
	test("a", list("a", "a", "b", "b", "c", "c", "d", "d"), "a")

#undef SECURITY_OFFICER_DEPARTMENTS


code:
/**
 * Check that standard food items fit on the serving tray
 */
/datum/unit_test/servingtray/Run()
	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
	var/obj/structure/table/the_table = allocate(/obj/structure/table)
	var/obj/item/storage/bag/tray/test_tray = allocate(/obj/item/storage/bag/tray)
	var/obj/item/reagent_containers/food/banana = allocate(/obj/item/food/rationpack)
	var/obj/item/food/the_bread = allocate(/obj/item/food/breadslice)
	var/obj/item/reagent_containers/food/sugarcookie = allocate(/obj/item/food/cookie/sugar)
	var/obj/item/clothing/under/jumpsuit = allocate(/obj/item/clothing/under/color/black)

	TEST_ASSERT_EQUAL((the_bread in test_tray.contents), FALSE, "The bread is on the serving tray at test start")

	// set the tray to single item mode the dirty way
	var/datum/component/storage/tray_storage = test_tray.GetComponent(/datum/component/storage)
	tray_storage.collection_mode = COLLECT_ONE

	test_tray.pre_attack(the_bread, human)

	TEST_ASSERT_EQUAL((the_bread in test_tray.contents), TRUE, "The bread did not get picked up by the serving tray")

	test_tray.pre_attack(banana, human)

	TEST_ASSERT_EQUAL((banana in test_tray.contents), TRUE, "The banana did not get picked up by the serving tray")

	the_table.attackby(test_tray, human)

	TEST_ASSERT_EQUAL(test_tray.contents.len, 0, "The serving tray did not drop all items on hitting the table")

	test_tray.pre_attack(sugarcookie, human)

	TEST_ASSERT_EQUAL((sugarcookie in test_tray.contents), TRUE, "The sugarcookie did not get picked up by the serving tray")

	human.equip_to_slot(jumpsuit, ITEM_SLOT_ICLOTHING)
	TEST_ASSERT(human.get_item_by_slot(ITEM_SLOT_ICLOTHING), "Human does not have jumpsuit on")

	human.equip_to_slot(test_tray, ITEM_SLOT_LPOCKET)
	TEST_ASSERT(human.get_item_by_slot(ITEM_SLOT_LPOCKET), "Serving tray failed to fit in the Left Pocket")

	human.equip_to_slot(test_tray, ITEM_SLOT_RPOCKET)
	TEST_ASSERT(human.get_item_by_slot(ITEM_SLOT_RPOCKET), "Serving tray failed to fit in the Right Pocket")

	test_tray.attack(human, human)

	TEST_ASSERT_EQUAL(test_tray.contents.len, 0, "The serving tray did not drop all items on hitting a human")


code:
/datum/unit_test/siunit/Run()
	TEST_ASSERT_EQUAL(siunit(0.5345, "A", 0), "535 mA", "")
	TEST_ASSERT_EQUAL(siunit(0.5344, "A", 0), "534 mA", "")
	TEST_ASSERT_EQUAL(siunit(-0.5344, "A", 0), "-534 mA", "")
	TEST_ASSERT_EQUAL(siunit_pressure(1.234, 1), "1.2 kPa", "") // test for pascal require *10e-3, as the game thinks in kPa, the proc siunit in Pa
	TEST_ASSERT_EQUAL(siunit_pressure(1.234, 2), "1.23 kPa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(1.234, 3), "1.234 kPa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(1, 4), "1 kPa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(0), "0 Pa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(1e3), "1 MPa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(999e3), "999 MPa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(999.9e3), "999.9 MPa" , "")
	TEST_ASSERT_EQUAL(siunit_pressure(999.9e3, 0), "1 GPa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(1e6), "1 GPa", "")
	TEST_ASSERT_EQUAL(siunit_pressure(3e17), "300000 PPa", "")


code:
/datum/unit_test/spawn_humans/Run()
	var/locs = block(run_loc_floor_bottom_left, run_loc_floor_top_right)

	for(var/I in 1 to 5)
		new /mob/living/carbon/human(pick(locs))

	sleep(50)


code:
///Unit test that spawns all mobs that can be spawned by golden slimes
/datum/unit_test/spawn_mobs

/datum/unit_test/spawn_mobs/Run()
	for(var/_animal in typesof(/mob/living/simple_animal))
		var/mob/living/simple_animal/animal = _animal
		if (initial(animal.gold_core_spawnable) == HOSTILE_SPAWN || initial(animal.gold_core_spawnable) == FRIENDLY_SPAWN)
			allocate(_animal)


code:
/datum/unit_test/species_whitelist_check/Run()
	for(var/typepath in subtypesof(/datum/species))
		var/datum/species/S = typepath
		if(initial(S.changesource_flags) == NONE)
			Fail("A species type was detected with no changesource flags: [S]")


code:
/datum/unit_test/stomach/Run()

	// Pause natural mob life so it can be handled entirely by the test
	SSmobs.pause()

	var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
	var/obj/item/food/hotdog/debug/fooditem = allocate(/obj/item/food/hotdog/debug)
	var/obj/item/organ/stomach/belly = human.getorganslot(ORGAN_SLOT_STOMACH)
	var/obj/item/reagent_containers/pill/pill = allocate(/obj/item/reagent_containers/pill)
	var/datum/reagent/drug/methamphetamine/meth = /datum/reagent/drug/methamphetamine

	TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human somehow has ketchup before eating")

	fooditem.attack(human, human)

	TEST_ASSERT(belly.reagents.has_reagent(/datum/reagent/consumable/ketchup), "Stomach doesn't have ketchup after eating")
	TEST_ASSERT_EQUAL(human.reagents.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human body has ketchup after eating it should only be in the stomach")

	//Give them meth and let it kick in
	pill.reagents.add_reagent(meth, 1.9 * initial(meth.metabolization_rate) * SSMOBS_DT)
	pill.attack(human, human)
	human.Life(SSMOBS_DT)

	TEST_ASSERT(human.reagents.has_reagent(meth), "Human body does not have meth after life tick")
	TEST_ASSERT(human.has_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine), "Human consumed meth, but did not gain movespeed modifier")

	belly.Remove(human)
	human.reagents.remove_all(human.reagents.total_volume)

	TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human has reagents after clearing")

	fooditem.attack(human, human)

	TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human has ketchup without a stomach")

/datum/unit_test/stomach/Destroy()
	SSmobs.ignite()
	return ..()


code:
/datum/unit_test/strip_menu_ui_status/Run()
	// We just need something that doesn't have strippable by default, so we can add it ourselves.
	var/obj/target = allocate(/obj/item/pen, run_loc_floor_bottom_left)
	var/datum/element/strippable/strippable = target.AddElement(/datum/element/strippable, list())

	var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human, run_loc_floor_bottom_left)
	ADD_TRAIT(user, TRAIT_PRESERVE_UI_WITHOUT_CLIENT, TRAIT_SOURCE_UNIT_TESTS)

	var/datum/strip_menu/strip_menu = allocate(/datum/strip_menu, target, strippable)

	var/ui_state = strip_menu.ui_state(user)

	TEST_ASSERT_EQUAL(strip_menu.ui_status(user, ui_state), UI_INTERACTIVE, "Perfect conditions were not interactive.")

	user.set_body_position(LYING_DOWN)
	// Necessary for tying shoes.
	TEST_ASSERT_EQUAL(strip_menu.ui_status(user, ui_state), UI_INTERACTIVE, "Lying down was not interactive.")

	user.forceMove(locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
	TEST_ASSERT_EQUAL(strip_menu.ui_status(user, ui_state), UI_UPDATE, "Being too far away while lying down was not update-only.")

	user.set_body_position(STANDING_UP)
	TEST_ASSERT_EQUAL(strip_menu.ui_status(user, ui_state), UI_UPDATE, "Being too far away while standing up was not update-only.")

	var/handcuffs = allocate(/obj/item/restraints/handcuffs, user)
	user.forceMove(target.loc)
	user.set_handcuffed(handcuffs)
	user.update_handcuffed()
	TEST_ASSERT_EQUAL(strip_menu.ui_status(user, ui_state), UI_UPDATE, "Being within range but cuffed was not update-only.")
	user.set_handcuffed(null)
	qdel(handcuffs)

	user.set_body_position(LYING_DOWN)
	user.death()
	TEST_ASSERT_EQUAL(strip_menu.ui_status(user, ui_state), UI_UPDATE, "Being within range but dead was not update-only.")

	var/mob/dead/observer/observer = allocate(/mob/dead/observer)
	// observers set their own turf, so we can't just pass it into allocate
	observer.forceMove(run_loc_floor_bottom_left)

	// A mocked client is needed for providing view
	var/datum/client_interface/mock_client = new
	observer.mock_client = mock_client
	ADD_TRAIT(observer, TRAIT_PRESERVE_UI_WITHOUT_CLIENT, TRAIT_SOURCE_UNIT_TESTS)
	TEST_ASSERT_EQUAL(strip_menu.ui_status(observer, ui_state), UI_UPDATE, "Being within range but an observer was not update-only.")

	var/mob/living/silicon/robot/borg = allocate(/mob/living/silicon/robot, run_loc_floor_bottom_left)
	ADD_TRAIT(borg, TRAIT_PRESERVE_UI_WITHOUT_CLIENT, TRAIT_SOURCE_UNIT_TESTS)
	TEST_ASSERT_EQUAL(strip_menu.ui_status(borg, ui_state), UI_INTERACTIVE, "Being within range as a borg was not interactive.")

	// Borgs can normally access tgui's regardless of position if it's within view range.
	// This makes sense for machinery, but not for this abstract UI.
	borg.forceMove(locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
	TEST_ASSERT_EQUAL(strip_menu.ui_status(borg, ui_state), UI_UPDATE, "Being too far away as a borg was not update-only.")

	var/mob/living/carbon/alien/rouny = allocate(/mob/living/carbon/alien, run_loc_floor_bottom_left)
	ADD_TRAIT(rouny, TRAIT_PRESERVE_UI_WITHOUT_CLIENT, TRAIT_SOURCE_UNIT_TESTS)
	TEST_ASSERT_EQUAL(strip_menu.ui_status(rouny, ui_state), UI_INTERACTIVE, "Being within range as a xeno was not interactive.")

	var/mob/living/simple_animal/pet/dog/corgi/corgi = allocate(/mob/living/simple_animal/pet/dog/corgi, run_loc_floor_bottom_left)
	TEST_ASSERT_EQUAL(strip_menu.ui_status(corgi, ui_state), UI_UPDATE, "Being within range as a corgi was not update-only.")


code:
/datum/unit_test/subsystem_init/Run()
	for(var/i in Master.subsystems)
		var/datum/controller/subsystem/ss = i
		if(ss.flags & SS_NO_INIT)
			continue
		if(!ss.initialized)
			Fail("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.")


code:
/* SEPTIC EDIT REMOVAL
/datum/unit_test/amputation/Run()
	var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human)
	var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)

	TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 0, "Patient is somehow missing limbs before surgery")

	var/datum/surgery/amputation/surgery = new(patient, BODY_ZONE_R_ARM, patient.get_bodypart(BODY_ZONE_R_ARM))

	var/datum/surgery_step/sever_limb/sever_limb = new
	sever_limb.success(user, patient, BODY_ZONE_R_ARM, null, surgery)

	TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 1, "Patient did not lose any limbs")
	TEST_ASSERT_EQUAL(patient.get_missing_limbs()[1], BODY_ZONE_R_ARM, "Patient is missing a limb that isn't the one we operated on")

/datum/unit_test/brain_surgery/Run()
	var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human)
	patient.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_SURGERY)
	patient.setOrganLoss(ORGAN_SLOT_BRAIN, 20)

	TEST_ASSERT(patient.has_trauma_type(), "Patient does not have any traumas, despite being given one")

	var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)

	var/datum/surgery_step/fix_brain/fix_brain = new
	fix_brain.success(user, patient)

	TEST_ASSERT(!patient.has_trauma_type(), "Patient kept their brain trauma after brain surgery")
	TEST_ASSERT(patient.getOrganLoss(ORGAN_SLOT_BRAIN) < 20, "Patient did not heal their brain damage after brain surgery")

/datum/unit_test/head_transplant/Run()
	var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)
	var/mob/living/carbon/human/alice = allocate(/mob/living/carbon/human)
	var/mob/living/carbon/human/bob = allocate(/mob/living/carbon/human)

	alice.fully_replace_character_name(null, "Alice")
	bob.fully_replace_character_name(null, "Bob")

	var/obj/item/bodypart/head/alices_head = alice.get_bodypart(BODY_ZONE_HEAD)
	alices_head.drop_limb()

	var/obj/item/bodypart/head/bobs_head = bob.get_bodypart(BODY_ZONE_HEAD)
	bobs_head.drop_limb()

	TEST_ASSERT_EQUAL(alice.get_bodypart(BODY_ZONE_HEAD), null, "Alice still has a head after dismemberment")
	TEST_ASSERT_EQUAL(alice.get_visible_name(), "Unknown", "Alice's head was dismembered, but they are not Unknown")

	TEST_ASSERT_EQUAL(bobs_head.real_name, "Bob", "Bob's head does not remember that it is from Bob")

	// Put Bob's head onto Alice's body
	var/datum/surgery_step/add_prosthetic/add_prosthetic = new
	user.put_in_active_hand(bobs_head)
	add_prosthetic.success(user, alice, BODY_ZONE_HEAD, bobs_head)

	TEST_ASSERT(!isnull(alice.get_bodypart(BODY_ZONE_HEAD)), "Alice has no head after prosthetic replacement")
	TEST_ASSERT_EQUAL(alice.get_visible_name(), "Bob", "Bob's head was transplanted onto Alice's body, but their name is not Bob")

/datum/unit_test/multiple_surgeries/Run()
	var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)
	var/mob/living/carbon/human/patient_zero = allocate(/mob/living/carbon/human)
	var/mob/living/carbon/human/patient_one = allocate(/mob/living/carbon/human)

	var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel)

	var/datum/surgery_step/incise/surgery_step = new
	var/datum/surgery/organ_manipulation/surgery_for_zero = new

	INVOKE_ASYNC(surgery_step, /datum/surgery_step/proc/initiate, user, patient_zero, BODY_ZONE_CHEST, scalpel, surgery_for_zero)
	TEST_ASSERT(surgery_for_zero.step_in_progress, "Surgery on patient zero was not initiated")

	var/datum/surgery/organ_manipulation/surgery_for_one = new

	// Without waiting for the incision to complete, try to start a new surgery
	TEST_ASSERT(!surgery_step.initiate(user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one), "Was allowed to start a second surgery without the rod of asclepius")
	TEST_ASSERT(!surgery_for_one.step_in_progress, "Surgery for patient one is somehow in progress, despite not initiating")

	user.apply_status_effect(STATUS_EFFECT_HIPPOCRATIC_OATH)
	INVOKE_ASYNC(surgery_step, /datum/surgery_step/proc/initiate, user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one)
	TEST_ASSERT(surgery_for_one.step_in_progress, "Surgery on patient one was not initiated, despite having rod of asclepius")

/datum/unit_test/tend_wounds/Run()
	var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human)
	patient.take_overall_damage(100, 100)

	var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)

	// Test that tending wounds actually lowers damage
	var/datum/surgery_step/heal/brute/basic/basic_brute_heal = new
	basic_brute_heal.success(user, patient, BODY_ZONE_CHEST)
	TEST_ASSERT(patient.getBruteLoss() < 100, "Tending brute wounds didn't lower brute damage ([patient.getBruteLoss()])")

	var/datum/surgery_step/heal/burn/basic/basic_burn_heal = new
	basic_burn_heal.success(user, patient, BODY_ZONE_CHEST)
	TEST_ASSERT(patient.getFireLoss() < 100, "Tending burn wounds didn't lower burn damage ([patient.getFireLoss()])")

	// Test that wearing clothing lowers heal amount
	var/mob/living/carbon/human/naked_patient = allocate(/mob/living/carbon/human)
	naked_patient.take_overall_damage(100)

	var/mob/living/carbon/human/clothed_patient = allocate(/mob/living/carbon/human)
	clothed_patient.equipOutfit(/datum/outfit/job/doctor, TRUE)
	clothed_patient.take_overall_damage(100)

	basic_brute_heal.success(user, naked_patient, BODY_ZONE_CHEST)
	basic_brute_heal.success(user, clothed_patient, BODY_ZONE_CHEST)

	TEST_ASSERT(naked_patient.getBruteLoss() < clothed_patient.getBruteLoss(), "Naked patient did not heal more from wounds tending than a clothed patient")
*/


code:
/datum/unit_test/auto_teleporter_linking/Run()
	// Put down the teleporter machinery
	var/obj/machinery/teleport/hub/hub = allocate(/obj/machinery/teleport/hub)
	var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
	var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))

	TEST_ASSERT_EQUAL(hub.power_station, station, "Hub didn't link to the station")
	TEST_ASSERT_EQUAL(station.teleporter_console, computer, "Station didn't link to the teleporter console")
	TEST_ASSERT_EQUAL(station.teleporter_hub, hub, "Station didn't link to the hub")
	TEST_ASSERT_EQUAL(computer.power_station, station, "Teleporter console didn't link to the hub")


code:
/// Test that `TGUI_CREATE_MESSAGE` is correctly implemented
/datum/unit_test/tgui_create_message

/datum/unit_test/tgui_create_message/Run()
	var/type = "something/here"
	var/list/payload = list(
		"name" = "Terry McTider",
		"heads_caved" = 100,
		"accomplishments" = list(
			"nothing",
			"literally nothing",
			list(
				"something" = "just kidding",
			),
		),
	)

	var/message = TGUI_CREATE_MESSAGE(type, payload)

	// Ensure consistent output to compare by performing a round-trip.
	var/output = json_encode(json_decode(url_decode(message)))

	var/expected = json_encode(list(
		"type" = type,
		"payload" = payload,
	))

	TEST_ASSERT_EQUAL(expected, output, "TGUI_CREATE_MESSAGE didn't round trip properly")


code:
/datum/unit_test/timer_sanity/Run()
	if(SStimer.bucket_count < 0)
		Fail("SStimer is going into negative bucket count from something")


code:
/*

Usage:
Override /Run() to run your test code

Call Fail() to fail the test (You should specify a reason)

You may use /New() and /Destroy() for setup/teardown respectively

You can use the run_loc_floor_bottom_left and run_loc_floor_top_right to get turfs for testing

*/

GLOBAL_DATUM(current_test, /datum/unit_test)
GLOBAL_VAR_INIT(failed_any_test, FALSE)
GLOBAL_VAR(test_log)

/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
	var/priority = TEST_DEFAULT
	//internal shit
	var/focus = FALSE
	var/succeeded = TRUE
	var/list/allocated
	var/list/fail_reasons

	var/static/datum/space_level/reservation

/proc/cmp_unit_test_priority(datum/unit_test/a, datum/unit_test/b)
	return initial(a.priority) - initial(b.priority)

/datum/unit_test/New()
	if (isnull(reservation))
		var/datum/map_template/unit_tests/template = new
		reservation = template.load_new_z()

	allocated = new
	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)

	TEST_ASSERT(isfloorturf(run_loc_floor_bottom_left), "run_loc_floor_bottom_left was not a floor ([run_loc_floor_bottom_left])")
	TEST_ASSERT(isfloorturf(run_loc_floor_top_right), "run_loc_floor_top_right was not a floor ([run_loc_floor_top_right])")

/datum/unit_test/Destroy()
	QDEL_LIST(allocated)
	// clear the test area
	for (var/turf/turf in block(locate(1, 1, run_loc_floor_bottom_left.z), locate(world.maxx, world.maxy, run_loc_floor_bottom_left.z)))
		for (var/content in turf.contents)
			if (istype(content, /obj/effect/landmark))
				continue
			qdel(content)
	return ..()

/datum/unit_test/proc/Run()
	Fail("Run() called parent or not implemented")

/datum/unit_test/proc/Fail(reason = "No reason")
	succeeded = FALSE

	if(!istext(reason))
		reason = "FORMATTED: [reason != null ? reason : "NULL"]"

	LAZYADD(fail_reasons, reason)

/// Allocates an instance of the provided type, and places it somewhere in an available loc
/// Instances allocated through this proc will be destroyed when the test is over
/datum/unit_test/proc/allocate(type, ...)
	var/list/arguments = args.Copy(2)
	if (!arguments.len)
		arguments = list(run_loc_floor_bottom_left)
	else if (arguments[1] == null)
		arguments[1] = run_loc_floor_bottom_left
	var/instance = new type(arglist(arguments))
	allocated += instance
	return instance

/proc/RunUnitTest(test_path, list/test_results)
	var/datum/unit_test/test = new test_path

	GLOB.current_test = test
	var/duration = REALTIMEOFDAY

	test.Run()

	duration = REALTIMEOFDAY - duration
	GLOB.current_test = null
	GLOB.failed_any_test |= !test.succeeded

	var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [test_path] [duration / 10]s")
	var/list/fail_reasons = test.fail_reasons

	for(var/J in 1 to LAZYLEN(fail_reasons))
		log_entry += "\tREASON #[J]: [fail_reasons[J]]"
	var/message = log_entry.Join("\n")
	log_test(message)

	test_results[test_path] = list("status" = test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED, "message" = message, "name" = test_path)

	qdel(test)

/proc/RunUnitTests()
	CHECK_TICK

	var/list/tests_to_run = subtypesof(/datum/unit_test)
	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))
			tests_to_run = list(test_to_run)
			break

	tests_to_run = sortTim(tests_to_run, /proc/cmp_unit_test_priority)

	var/list/test_results = list()

	for(var/unit_path in tests_to_run)
		CHECK_TICK //We check tick first because the unit test we run last may be so expensive that checking tick will lock up this loop forever
		RunUnitTest(unit_path, test_results)

	var/file_name = "data/unit_tests.json"
	fdel(file_name)
	file(file_name) << json_encode(test_results)

	SSticker.force_ending = TRUE
	//We have to call this manually because del_text can preceed us, and SSticker doesn't fire in the post game
	SSticker.standard_reboot()

/datum/map_template/unit_tests
	name = "Unit Tests Zone"
	mappath = "_maps/templates/unit_tests.dmm"


code:
// Once upon a time, a Game Master decided to upgrade the wizard's spellbook to tgui.
// In doing so, he introduced an infinite loop that crashed many servers and made many wizards sad.
// May this never happen again.

/// Test loadouts for crashes, runtimes, stack traces and infinite loops. No ASSERTs necessary.
/datum/unit_test/wizard_loadout/Run()
	for(var/loadout in ALL_WIZARD_LOADOUTS)
		var/obj/item/spellbook/wizard_book = allocate(/obj/item/spellbook)
		var/mob/living/carbon/human/wizard = allocate(/mob/living/carbon/human)
		wizard.mind_initialize()
		wizard.put_in_active_hand(wizard_book, forced = TRUE)
		wizard_book.wizard_loadout(wizard, loadout)


code:
// A collection of pre-set uplinks, for admin spawns.

// Radio-like uplink; not an actual radio because this uplink is most commonly
// used for nuke ops, for whom opening the radio GUI and the uplink GUI
// simultaneously is an annoying distraction.
/obj/item/uplink
	name = "station bounced radio"
	icon = 'icons/obj/radio.dmi'
	icon_state = "radio"
	inhand_icon_state = "walkietalkie"
	worn_icon_state = "radio"
	desc = "A basic handheld radio that communicates with local telecommunication networks."
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	dog_fashion = /datum/dog_fashion/back

	flags_1 = CONDUCT_1
	slot_flags = ITEM_SLOT_BELT
	throw_speed = 3
	throw_range = 7
	w_class = WEIGHT_CLASS_SMALL

	/// The uplink flag for this type.
	/// See [`code/__DEFINES/uplink.dm`]
	var/uplink_flag = UPLINK_TRAITORS

/obj/item/uplink/Initialize(mapload, owner, tc_amount = 20)
	. = ..()
	AddComponent(/datum/component/uplink, owner, FALSE, TRUE, uplink_flag, tc_amount)

/obj/item/uplink/debug
	name = "debug uplink"

/obj/item/uplink/debug/Initialize(mapload, owner, tc_amount = 9000)
	. = ..()
	var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
	hidden_uplink.name = "debug uplink"
	hidden_uplink.debug = TRUE

/obj/item/uplink/nuclear
	uplink_flag = UPLINK_NUKE_OPS

/obj/item/uplink/nuclear/debug
	name = "debug nuclear uplink"
	uplink_flag = UPLINK_NUKE_OPS

/obj/item/uplink/nuclear/debug/Initialize(mapload, owner, tc_amount = 9000)
	. = ..()
	var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
	hidden_uplink.name = "debug nuclear uplink"
	hidden_uplink.debug = TRUE

/obj/item/uplink/nuclear_restricted
	uplink_flag = UPLINK_NUKE_OPS

/obj/item/uplink/nuclear_restricted/Initialize(mapload)
	. = ..()
	var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
	hidden_uplink.allow_restricted = FALSE

/obj/item/uplink/clownop
	uplink_flag = UPLINK_CLOWN_OPS

/obj/item/uplink/old
	name = "dusty radio"
	desc = "A dusty looking radio."

/obj/item/uplink/old/Initialize(mapload, owner, tc_amount = 10)
	. = ..()
	var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
	hidden_uplink.name = "dusty radio"

// Multitool uplink
/obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 20)
	. = ..()
	AddComponent(/datum/component/uplink, owner, FALSE, TRUE, UPLINK_TRAITORS, tc_amount)

// Pen uplink
/obj/item/pen/uplink/Initialize(mapload, owner, tc_amount = 20)
	. = ..()
	AddComponent(/datum/component/uplink, owner, TRUE, FALSE, UPLINK_TRAITORS, tc_amount)