# dm/host unit tests in one command: make -C dm/host/tests GAME=<base game title ID> run (takes seconds; build output goes to build/ and is not committed)
B = build
PYTHON ?= python3
# SAN: extra compiler flags for a sanitizer or coverage build (make asan, make tsan-threads and make coverage below set it, with B = a build folder of their own); empty otherwise
SAN ?=
CLANG = clang $(SAN)
# stderr that the plain run hides is kept in sanitizer builds: the sanitizers report there
QUIET = $(if $(findstring -fsanitize,$(SAN)),,2>/dev/null)
.DEFAULT_GOAL := run
# game profile header (dm.h needs game_profile.h, generated by the host Makefile into ../gen/<profile>/)
# tests that use the game profile need GAME=<base game title ID> (there is no default game); GINC only expands when used, so tests without the profile build as before
GINC = $(if $(strip $(GAME)),-I../gen/$(GAME) -I../../games/$(GAME),$(error GAME is not set: make GAME=<base game title ID>: $(shell python3 ../../games/game.py list | awk -F'\t' '{printf "%s (%s)  ", $$1, $$3}')))
$(B)/t_fs $(B)/t_fs_case $(B)/t_fs_commit_list $(B)/t_fs_commit_list_tsan: ../romfs_image.h
GAME_H = ../gen/$(GAME)/game_profile.h
run: $(B)/t_net_eventfd $(B)/t_language $(B)/t_motion $(B)/t_heap_size $(B)/t_touch $(B)/t_gesture $(B)/t_oe $(B)/t_fs $(B)/t_fs_commit_list $(B)/t_thread_leak $(B)/t_fs_case $(B)/t_audio $(B)/t_output_stream $(B)/t_audio_spsc $(B)/t_swkbd $(B)/t_altstack $(B)/t_condvar $(B)/t_condvar_old $(B)/t_pollwait $(B)/t_pctl $(B)/t_sf $(B)/t_time $(B)/t_shared_font $(B)/t_vorbis $(B)/t_vorbis_divides $(B)/t_hid $(B)/t_haptics $(B)/t_haptics_apple $(B)/t_cheats $(B)/t_pad_hid $(B)/t_input_map $(B)/t_launcher_contract $(B)/t_profile_runtime
	$(B)/t_language
	$(B)/t_heap_size
	$(B)/t_touch
	$(B)/t_gesture
	$(B)/t_oe $(QUIET)
	$(B)/t_fs $(QUIET)
	TMPDIR="$(abspath $(B))" $(B)/t_fs_commit_list
	$(B)/t_thread_leak
	TMPDIR="$(abspath $(B))" $(B)/t_fs_case $(QUIET)
	TMPDIR="$(abspath $(B))" $(B)/t_save_sync $(QUIET)
	TMPDIR="$(abspath $(B))" $(B)/t_save_sync_metadata $(QUIET)
	$(B)/t_audio
	$(B)/t_output_stream
	$(B)/t_audio_spsc
	$(B)/t_audio_stats
	$(B)/t_fpslock
	$(B)/t_uecvar
	$(B)/t_ue_heap_span
	$(B)/t_audren_work
	$(B)/t_audren_layout
	$(B)/t_swkbd
	$(B)/t_altstack
	$(B)/t_bigstack
	$(B)/t_condvar
	$(B)/t_condvar_old
	$(B)/t_pollwait
	$(B)/t_preempt
	$(B)/t_preempt --all-peers
	$(B)/t_pctl
	$(B)/t_net_eventfd
	$(B)/t_sf
	$(B)/t_time
	$(B)/t_shared_font
	TMPDIR="$(abspath $(B))" $(B)/t_profile_runtime
	$(B)/t_system_event
	$(B)/t_vorbis
	$(B)/t_vorbis_divides
	$(B)/t_movie_vpx
	$(B)/t_codec_video
	$(B)/t_movie_avc
	$(B)/t_nv
	$(B)/t_nfc
	$(B)/t_motion
	$(B)/t_motion_apple
	$(B)/t_pad_apple
	$(B)/t_home_quit
	$(B)/t_home_quit_mac
	$(B)/t_motion_pro
	$(B)/t_motion_pro_mac
	TMPDIR="$(abspath $(B))" $(B)/t_hid
	$(B)/t_pad_hid
	$(B)/t_input_map
	$(B)/t_launcher_contract
	$(B)/t_haptics
	$(B)/t_haptics_apple
	TMPDIR="$(abspath $(B))" $(B)/t_cheats $(QUIET)
	$(B)/t_nro
	$(B)/t_symbols
	$(B)/t_pad_map
	$(B)/t_pad_motion
	$(B)/t_controllers_mac
run: $(B)/t_nro
run: $(B)/t_symbols
$(B)/t_symbols: t_symbols.c ../hle.c ../loader.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -Wno-deprecated-declarations -I.. $(GINC) -o $@ t_symbols.c ../hle_profile.c
.PHONY: symbols
symbols: $(B)/t_symbols
	$(B)/t_symbols
run: $(B)/t_system_event
run: $(B)/t_audren_work
$(B)/t_audren_work: t_audren_work.c ../hle_audren.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_audren_work.c
.PHONY: audren-work
audren-work: $(B)/t_audren_work
	$(B)/t_audren_work
run: $(B)/t_audren_layout
$(B)/t_audren_layout: t_audren_layout.c ../hle_audren.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_audren_layout.c
.PHONY: audren-layout
audren-layout: $(B)/t_audren_layout
	$(B)/t_audren_layout
$(B)/t_system_event: t_system_event.c ../hle_thread.c ../hle_sdk16.c ../hle_misc.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -Wno-frame-address -I.. $(GINC) -o $@ t_system_event.c ../hle_misc.c ../hle_bigstack.c ../hle_profile.c
.PHONY: system-event
system-event: $(B)/t_system_event
	$(B)/t_system_event
$(B)/t_nro: t_nro.c ../loader.c ../hle_ro.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Wno-unused-function -Wno-deprecated-declarations -I.. $(GINC) -o $@ t_nro.c ../hle_profile.c
.PHONY: nro
nro: $(B)/t_nro
	$(B)/t_nro
# the cheat engine (hle_cheats.c, compiled into the test with stubs for the loader, the heap and the keys)
$(B)/t_cheats: t_cheats.c ../hle_cheats.c ../dm.h ../dm_input.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_cheats.c ../hle_profile.c
.PHONY: cheats
cheats: $(B)/t_cheats
	TMPDIR="$(abspath $(B))" $(B)/t_cheats
# Regenerated on every make (FORCE), by the host Makefile: a header from an older game.py lacks newer defines; game.py writes it only when it changes, so an unchanged header rebuilds no test
$(GAME_H): FORCE
	$(MAKE) -C .. game_profile GAME=$(GAME)
$(B)/t_heap_size: t_heap_size.c ../hle_svc.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -fsanitize=undefined -I.. $(GINC) -o $@ t_heap_size.c ../hle_profile.c
.PHONY: heap_size
heap_size: $(B)/t_heap_size
	$(B)/t_heap_size
$(B)/t_touch: t_touch.c ../hle_touch.c ../front.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_touch.c ../hle_touch.c
$(B)/t_gesture: t_gesture.c ../hle_touch.c ../front.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Wno-unused-function -I.. $(GINC) -o $@ t_gesture.c ../hle_touch.c
$(B)/t_thread_leak: t_thread_leak.c ../thread.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_thread_leak.c
$(B)/t_fs: t_fs.c t_fs_stub.c ../hle_fs.c ../save_sync.c ../save_sync.h ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) ../hle_profile.c ../hle_profile.h | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_fs.c t_fs_stub.c ../hle_bigstack.c ../save_sync.c ../hle_profile.c
# Inject only the folder handle allocation; directory I/O remains real.
$(B)/t_fs_directory: t_fs_directory.c t_fs_stub.c ../hle_fs.c ../romfs_image.h ../save_sync.c ../save_sync.h ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) ../hle_profile.c ../hle_profile.h | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_fs_directory.c t_fs_stub.c ../hle_bigstack.c ../save_sync.c ../hle_profile.c
.PHONY: fs-directory
fs-directory: $(B)/t_fs_directory
	TMPDIR="$(abspath $(B))" $(B)/t_fs_directory
run: fs-directory
$(B)/t_fs_commit_list: t_fs_commit_list.c t_fs_stub.c ../hle_fs.c ../save_sync.c ../save_sync.h ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) ../hle_profile.c ../hle_profile.h | $(B)
	$(CLANG) -O1 -Wall -Wno-unused-function -I.. $(GINC) -o $@ t_fs_commit_list.c t_fs_stub.c ../hle_bigstack.c ../save_sync.c ../hle_profile.c
$(B)/t_fs_commit_list_tsan: t_fs_commit_list.c t_fs_stub.c ../hle_fs.c ../save_sync.c ../save_sync.h ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) ../hle_profile.c ../hle_profile.h | $(B)
	$(CLANG) -O1 -g -fsanitize=thread -Wall -Wno-unused-function -I.. $(GINC) -o $@ t_fs_commit_list.c t_fs_stub.c ../hle_bigstack.c ../save_sync.c ../hle_profile.c
.PHONY: fs-tsan
fs-tsan: $(B)/t_fs_commit_list_tsan
	TMPDIR="$(abspath $(B))" $(B)/t_fs_commit_list_tsan
$(B)/t_fs_case: t_fs_case.c ../hle_fs.c ../save_sync.c ../save_sync.h ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) ../hle_profile.c ../hle_profile.h | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_fs_case.c ../hle_bigstack.c ../save_sync.c ../hle_profile.c
run: $(B)/t_save_sync $(B)/t_save_sync_metadata
$(B)/t_save_sync: t_save_sync.c t_fs_stub.c ../hle_fs.c ../save_sync.c ../save_sync.h ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) ../hle_profile.c ../hle_profile.h | $(B)
	$(CLANG) -O1 -Wall -Wno-unused-function -I.. $(GINC) -o $@ t_save_sync.c t_fs_stub.c ../hle_bigstack.c ../save_sync.c ../hle_profile.c
$(B)/t_save_sync_metadata: t_save_sync_metadata.c ../save_sync.c ../save_sync.h | $(B)
	$(CLANG) -O1 -Wall -Wno-unused-function -I.. -o $@ t_save_sync_metadata.c
.PHONY: save-sync
save-sync: $(B)/t_save_sync $(B)/t_save_sync_metadata
	TMPDIR="$(abspath $(B))" $(B)/t_save_sync $(QUIET)
	TMPDIR="$(abspath $(B))" $(B)/t_save_sync_metadata $(QUIET)
$(B)/t_audio: t_audio.c ../hle_audio.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_audio.c ../hle_bigstack.c -framework AudioToolbox
$(B)/t_output_stream: t_output_stream.c ../hle_audio.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Wno-unused-function -I.. $(GINC) -o $@ t_output_stream.c ../hle_bigstack.c -framework AudioToolbox
$(B)/t_audio_spsc: t_audio_spsc.c ../hle_audio.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_audio_spsc.c ../hle_bigstack.c -framework AudioToolbox
$(B)/t_audio_stats: t_audio_stats.c ../hle_audio.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_audio_stats.c ../hle_bigstack.c -framework AudioToolbox
run: $(B)/t_audio_stats
$(B)/t_audio_device: t_audio_device.c ../hle_audio.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_audio_device.c ../hle_bigstack.c -framework AudioToolbox
.PHONY: audio-device
audio-device: $(B)/t_audio_device
	$(B)/t_audio_device
run: audio-device
$(B)/t_audio_spsc_tsan: t_audio_spsc.c ../hle_audio.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -O1 -fsanitize=thread -Wno-unused-function -I.. $(GINC) -o $@ t_audio_spsc.c ../hle_bigstack.c -framework AudioToolbox
run: $(B)/t_fpslock
$(B)/t_fpslock: t_fpslock.c ../hle_fpslock.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wno-unused-function $(GINC) -o $@ t_fpslock.c ../hle_profile.c
run: $(B)/t_ue_heap_span
$(B)/t_ue_heap_span: t_ue_heap_span.c ../hle_uemalloc.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function $(GINC) -o $@ t_ue_heap_span.c
run: $(B)/t_uecvar
$(B)/t_uecvar: t_uecvar.c ../hle_uecvar.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wno-unused-function $(GINC) -o $@ t_uecvar.c ../hle_profile.c
$(B)/t_swkbd: t_swkbd.c ../hle_swkbd.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_swkbd.c ../hle_swkbd.c
run: $(B)/t_bigstack
$(B)/t_bigstack: t_bigstack.c ../hle_bigstack.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Wno-unused-function -I.. $(GINC) -o $@ t_bigstack.c ../hle_bigstack.c
run: watch-sample
$(B)/t_watch_sample: t_watch_sample.c ../crash.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_watch_sample.c
.PHONY: watch-sample
watch-sample: $(B)/t_watch_sample
	$(B)/t_watch_sample
$(B)/t_altstack: t_altstack.c t_altstack_stub.c ../crash.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_altstack.c t_altstack_stub.c ../crash.c
$(B)/t_net_eventfd: t_net_eventfd.c ../hle_net.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -fsanitize=address,undefined -I.. $(GINC) -o $@ t_net_eventfd.c

$(B)/t_pctl: t_pctl.c ../hle_misc.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_pctl.c
$(B)/t_sf: t_sf.c ../hle_sf.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_sf.c
$(B)/t_shared_font: t_shared_font.c ../hle_sdk16.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -Wno-frame-address -I.. $(GINC) -o $@ t_shared_font.c ../hle_profile.c
run: $(B)/t_nv
$(B)/t_nv: t_nv.c ../../gfx/hle_nv.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_nv.c
# nn::nfc answered without nfc:user (NFC disabled), beside the nfp no-tag answers
run: $(B)/t_nfc
$(B)/t_nfc: t_nfc.c ../hle_sdk16.c ../hle.h ../dm.h $(GAME_H) ../hle_profile.c ../hle_profile.h | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -Wno-frame-address -I.. $(GINC) -o $@ t_nfc.c ../hle_profile.c
.PHONY: nfc
nfc: $(B)/t_nfc
	$(B)/t_nfc
$(B)/t_language: t_language.c ../hle_sdk16.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -Wno-frame-address -fsanitize=address,undefined -I.. $(GINC) -o $@ t_language.c ../hle_profile.c
$(B)/t_motion: t_motion.c ../motion_fusion.h | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -o $@ t_motion.c -lm
run: $(B)/t_motion_apple
$(B)/t_motion_apple: t_motion_apple.m ../motion_apple.h ../dm_input.h | $(B)
	$(CLANG) -O1 -g -fobjc-arc -Wall -Werror -I.. -o $@ t_motion_apple.m -framework Foundation -framework GameController
run: $(B)/t_pad_apple
$(B)/t_pad_apple: t_pad_apple.m ../pad_apple.h ../dm_input.h | $(B)
	$(CLANG) -O1 -g -fobjc-arc -Wall -Werror -I.. -o $@ t_pad_apple.m -framework Foundation -framework GameController
run: $(B)/t_home_quit $(B)/t_home_quit_mac
$(B)/t_home_quit: t_home_quit.c ../home_quit.h | $(B)
	$(CLANG) -O1 -g -Wall -Werror -I.. -o $@ t_home_quit.c
$(B)/t_home_quit_mac: t_home_quit_mac.m ../home_quit_mac.h | $(B)
	$(CLANG) -O1 -g -fobjc-arc -Wall -Werror -Wno-unused-function -I.. -o $@ t_home_quit_mac.m -framework Cocoa -framework QuartzCore
run: $(B)/t_motion_pro $(B)/t_motion_pro_mac
$(B)/t_motion_pro: t_motion_pro.c ../motion_pro.h | $(B)
	$(CLANG) -O1 -g -Wall -Werror -fsanitize=address,undefined -o $@ t_motion_pro.c
$(B)/t_motion_pro_mac: t_motion_pro_mac.m ../motion_pro_mac.h ../motion_pro.h ../dm_input.h | $(B)
	$(CLANG) -O1 -g -fobjc-arc -Wall -Werror -Wno-unused-function -fsanitize=address,undefined -I.. -o $@ t_motion_pro_mac.m -framework Foundation -framework GameController -framework IOKit
$(B)/t_input_map: t_input_map.c ../input_map.h ../dm_input.h | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -I.. -o $@ t_input_map.c
$(B)/t_launcher_contract: t_launcher_contract.c ../input_map.h ../dm_input.h | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -I.. -o $@ t_launcher_contract.c
$(B)/t_hid: t_hid.c ../hle_hid.c ../motion_fusion.h ../hle_haptics.c ../hle_haptics.h ../pad_slots.h ../dm_input.h ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_hid.c ../hle_haptics.c
$(B)/t_hid_tsan: t_hid.c ../hle_hid.c ../motion_fusion.h ../hle_haptics.c ../hle_haptics.h ../pad_slots.h ../dm_input.h ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -fsanitize=thread -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_hid.c ../hle_haptics.c
$(B)/t_haptics: t_haptics.c ../hle_haptics.c ../hle_haptics.h ../dm_input.h | $(B)
	$(CLANG) -O1 -g -Wall -Werror -I.. -o $@ t_haptics.c ../hle_haptics.c
$(B)/t_haptics_apple: t_haptics_apple.m ../haptics_apple.m ../haptics_apple.h ../hle_haptics.c ../hle_haptics.h | $(B)
	$(CLANG) -O1 -g -fobjc-arc -Wall -Werror -I.. -o $@ t_haptics_apple.m ../hle_haptics.c -framework Cocoa -framework GameController -framework CoreHaptics -framework IOKit
$(B)/t_time: t_time.c ../hle_time.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_time.c
$(B)/t_oe: t_oe.c ../hle_oe.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -I.. $(GINC) -o $@ t_oe.c ../hle_profile.c
$(B)/t_condvar: t_condvar.c t_condvar_stub.c ../hle_thread.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -DDM_TEST_HOOKS -I.. $(GINC) -o $@ t_condvar.c t_condvar_stub.c ../hle_thread.c ../hle_profile.c
$(B)/t_condvar_old: t_condvar.c t_condvar_stub.c ../hle_thread.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wno-unused-function -DDM_TEST_HOOKS -DDM_TEST_CV_OLD_ORDER -I.. $(GINC) -o $@ t_condvar.c t_condvar_stub.c ../hle_thread.c ../hle_profile.c
$(B)/t_pollwait: t_pollwait.c t_condvar_stub.c ../hle_thread.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_pollwait.c t_condvar_stub.c ../hle_profile.c
$(B)/t_preempt: t_preempt.c t_condvar_stub.c ../hle_thread.c ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_preempt.c t_condvar_stub.c ../hle_profile.c
.PHONY: preempt
preempt: $(B)/t_preempt
	$(B)/t_preempt
	$(B)/t_preempt --all-peers
run: $(B)/t_preempt
# the Vorbis tests build their own packets and need no game profile (they build without GAME)
$(B)/t_vorbis: t_vorbis.c ../../third_party/stb_vorbis/stb_vorbis.c | $(B)
	$(CLANG) -O1 -g -Wno-unused-function -Wno-tautological-compare -o $@ t_vorbis.c
$(B)/t_vorbis_divides: t_vorbis.c ../../third_party/stb_vorbis/stb_vorbis.c | $(B)
	$(CLANG) -O1 -g -DSTB_VORBIS_DIVIDES_IN_RESIDUE -Wno-unused-function -Wno-tautological-compare -o $@ t_vorbis.c
run: $(B)/t_movie_vpx
run: $(B)/t_codec_video
run: $(B)/t_movie_avc
$(B)/t_movie_avc: t_movie_avc.c ../hle_movie.c ../hle_movie_avc.c ../hle_movie_avc.h $(GAME_H) ../gen/vpx/libvpx.a | $(B)
	$(CLANG) -O1 -g -Wno-unused-function -Wno-tautological-compare $(GINC) -I../../third_party/libvpx -I../../third_party/libvpx/config -I../../third_party/stb_vorbis -Wl,-dead_strip -o $@ t_movie_avc.c ../gen/vpx/libvpx.a \
		-framework AudioToolbox -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework CoreFoundation
$(B)/t_movie_vpx: t_movie_vpx.c ../hle_movie.c $(GAME_H) ../gen/vpx/libvpx.a | $(B)
	$(CLANG) -O1 -g -Wno-unused-function -Wno-tautological-compare $(GINC) -I../../third_party/libvpx -I../../third_party/libvpx/config -I../../third_party/stb_vorbis -Wl,-dead_strip -o $@ t_movie_vpx.c ../gen/vpx/libvpx.a -framework AudioToolbox
$(B)/t_codec_video: t_codec_video.c ../hle_codec_video.c ../hle_movie.c ../hle_movie_avc.c ../hle_movie_avc.h ../hle_movie_av1.c ../hle_movie_av1.h ../hle_movie_yuv.h $(GAME_H) ../gen/vpx/libvpx.a ../gen/dav1d/libdav1d.a | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -Wno-tautological-compare -fsanitize=address,undefined $(GINC) -I../../third_party/libvpx -I../../third_party/libvpx/config -I../../third_party/dav1d/include -I../../third_party/stb_vorbis -o $@ t_codec_video.c ../gen/vpx/libvpx.a ../gen/dav1d/libdav1d.a \
		-framework AudioToolbox -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework CoreFoundation
../gen/vpx/libvpx.a: FORCE
	$(MAKE) -C .. GAME=$(GAME) gen/vpx/libvpx.a
../gen/dav1d/libdav1d.a: FORCE
	$(MAKE) -C .. GAME=$(GAME) gen/dav1d/libdav1d.a
.PHONY: FORCE
FORCE:
$(B):
	mkdir -p $@
.PHONY: run tsan
$(B)/t_resolution: t_resolution.c ../hle_fpslock.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -Wall -Werror -Wno-unused-function -fsanitize=undefined -I.. $(GINC) -o $@ t_resolution.c ../hle_profile.c
.PHONY: resolution
run: resolution
resolution: $(B)/t_resolution
	@for fps in 0 30 60; do for mode in 0 1 unset invalid; do $(B)/t_resolution $$mode $$fps native || exit 1; done; done
	$(B)/t_resolution 1 60 old
	$(B)/t_resolution 1 30 wrong
	@for fps in 0 30 60; do for mode in 0 1 unset invalid; do $(B)/t_resolution $$mode $$fps asia || exit 1; done; done
tsan: $(B)/t_audio_spsc_tsan $(B)/t_hid_tsan
	$(B)/t_audio_spsc_tsan
	TMPDIR="$(abspath $(B))" $(B)/t_hid_tsan

MEM_FLAGS = -O1 -Wall -Werror -Wno-unused-function -Imemory_profile -I..
MEM_DEPS = t_svc_memory.c memory_profile/game_profile.h ../hle_svc.c ../hle_guestthread.c ../hle.h ../dm.h ../hle_profile.c ../hle_profile.h
$(B)/t_svc_memory: $(MEM_DEPS) | $(B)
	$(CLANG) $(MEM_FLAGS) -fsanitize=undefined -o $@ t_svc_memory.c ../hle_profile.c
$(B)/t_svc_memory_unlimited: $(MEM_DEPS) | $(B)
	$(CLANG) $(MEM_FLAGS) -fsanitize=undefined -DDM_GAME_MEM_MAP_LIMIT=0 -o $@ t_svc_memory.c ../hle_profile.c
$(B)/t_svc_memory_16k: $(MEM_DEPS) | $(B)
	$(CLANG) $(MEM_FLAGS) -fsanitize=address,undefined -DTEST_PAGE_16K -o $@ t_svc_memory.c ../hle_profile.c
$(B)/t_svc_memory_tsan: $(MEM_DEPS) | $(B)
	$(CLANG) $(MEM_FLAGS) -fsanitize=thread -o $@ t_svc_memory.c ../hle_profile.c
$(B)/t_svc_memory_dirty: $(MEM_DEPS) ../../gfx/nvn/nvn_dirty.c ../../gfx/nvn/nvn_dirty.h | $(B)
	$(CLANG) $(MEM_FLAGS) -DTEST_REAL_DIRTY -o $@ t_svc_memory.c ../../gfx/nvn/nvn_dirty.c ../hle_profile.c
.PHONY: memory memory-tsan memory-dirty memory-profile
memory: $(B)/t_svc_memory $(B)/t_svc_memory_unlimited $(B)/t_svc_memory_16k
	$(B)/t_svc_memory
	$(B)/t_svc_memory_unlimited
	$(B)/t_svc_memory_16k
memory-tsan: $(B)/t_svc_memory_tsan
	$(B)/t_svc_memory_tsan
memory-dirty: $(B)/t_svc_memory_dirty
	$(B)/t_svc_memory_dirty
memory-profile: $(B)/t_svc_memory
	DM_TEST_BUILD=$(B) $(PYTHON) test_memory_profile.py
# The same test with the guest's memory in files (DM_GUEST_FILE, hle_svc.c), one file and then 64 MB files, made in the build folder; its last line says how many files held it
# (0: the files could not be made there, and the guest stayed anonymous)
GUEST_FILE_ENV = DM_GUEST_FILE=1 DM_GUEST_FILE_MIN_FREE=0 DM_GUEST_FILE_DIR="$(abspath $(B))"
.PHONY: memory-file
memory-file: $(B)/t_svc_memory $(B)/t_svc_memory_16k
	$(GUEST_FILE_ENV) $(B)/t_svc_memory
	$(GUEST_FILE_ENV) DM_GUEST_FILE_CHUNK=64M $(B)/t_svc_memory
	$(GUEST_FILE_ENV) DM_GUEST_FILE_CHUNK=64M $(B)/t_svc_memory_16k
run: memory memory-dirty memory-profile memory-file

# A native blocking notification wait must release the thread's guest core.
$(B)/t_notify_sched: t_notify_sched.c t_condvar_stub.c ../hle_thread.c ../hle_oe.c ../hle.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -DDM_TEST_THREAD_ENTER -I.. $(GINC) -o $@ t_notify_sched.c t_condvar_stub.c ../hle_profile.c
.PHONY: notify-scheduler
notify-scheduler: $(B)/t_notify_sched
	$(B)/t_notify_sched
	$(B)/t_notify_sched --all-peers
run: notify-scheduler

# Sanitizer builds (../../sanitizers/sanitizers.mk): make asan runs every test of run under ASan and UBSan; make tsan-threads runs the tests that start threads under TSan
# (t_audio_spsc, t_hid and t_svc_memory have their own TSan targets above: tsan, memory-tsan)
include ../../sanitizers/sanitizers.mk
.PHONY: asan tsan-threads tsan-threads-run
asan:
	@$(call san_fresh,$(B)/asan,$(ASAN_FLAGS))
	$(ASAN_ENV) $(MAKE) B=$(B)/asan SAN="$(ASAN_FLAGS)" run
TSAN_T = t_audio t_audio_stats t_audio_device t_thread_leak t_oe t_fs_commit_list t_fs_case t_save_sync t_condvar t_condvar_old t_preempt t_system_event t_notify_sched t_altstack
tsan-threads:
	@$(call san_fresh,$(B)/tsan,$(TSAN_FLAGS))
	$(TSAN_ENV) $(MAKE) B=$(B)/tsan SAN="$(TSAN_FLAGS)" tsan-threads-run
tsan-threads-run: $(addprefix $(B)/,$(TSAN_T))
	$(B)/t_audio
	$(B)/t_audio_stats
	$(B)/t_audio_device
	$(B)/t_thread_leak
	$(B)/t_oe
	TMPDIR="$(abspath $(B))" $(B)/t_fs_commit_list
	TMPDIR="$(abspath $(B))" $(B)/t_fs_case
	TMPDIR="$(abspath $(B))" $(B)/t_save_sync
	$(B)/t_condvar
	$(B)/t_condvar_old
	$(B)/t_preempt
	$(B)/t_preempt --all-peers
	$(B)/t_system_event
	$(B)/t_notify_sched
	$(B)/t_notify_sched --all-peers
	$(B)/t_altstack
	DM_QOS_MAP=1 $(B)/t_notify_sched
	DM_QOS_MAP=1 $(B)/t_notify_sched --all-peers
$(B)/t_pad_hid: t_pad_hid.c ../pad_hid.h ../pad_joycon.h ../motion_pro.h ../dm_input.h | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -fsanitize=address,undefined -I.. -o $@ t_pad_hid.c

# Line coverage (../../sanitizers/coverage.mk): make coverage builds every test of run into $(B)/cov with COV_FLAGS, runs them and writes $(B)/cov/coverage.lcov.
# It measures, it does not test: make -i runs every test even after one fails (the plain run reports that), so the coverage of the others is kept.
include ../../sanitizers/coverage.mk
.PHONY: coverage
coverage:
	@$(call san_fresh,$(B)/cov,$(COV_FLAGS))
	rm -rf $(B)/cov/prof
	LLVM_PROFILE_FILE="$(call cov_profile,$(B)/cov)" $(MAKE) -i B=$(B)/cov SAN="$(COV_FLAGS)" run
	@$(call cov_export,$(B)/cov)
# the runtime profile (hle_profile.c): a DM_PROFILE file, DM_PROF_* variables and the compiled fallback
$(B)/t_profile_runtime: t_profile_runtime.c ../hle_profile.c ../hle_profile.h ../dm.h $(GAME_H) | $(B)
	$(CLANG) -O1 -g -Wall -Werror -Wno-unused-function -I.. $(GINC) -o $@ t_profile_runtime.c ../hle_profile.c
.PHONY: profile-runtime
profile-runtime: $(B)/t_profile_runtime
	TMPDIR="$(abspath $(B))" $(B)/t_profile_runtime

run: $(B)/t_pad_map $(B)/t_pad_motion $(B)/t_controllers_mac
$(B)/t_pad_motion: t_pad_motion.c ../pad_motion.h ../dm_input.h | $(B)
	$(CLANG) -O1 -Wall -Werror -o $@ t_pad_motion.c
$(B)/t_pad_map: t_pad_map.c ../pad_map.h ../steam_input.h ../dm_input.h | $(B)
	$(CLANG) -O1 -Wall -Werror -o $@ t_pad_map.c
$(B)/t_controllers_mac: t_controllers_mac.m ../home_quit.h ../pad_hid.h ../pad_hid_mac.h ../pad_apple.h ../input_map.h ../input_preferences_mac.h ../controllers_mac.m ../controllers_mac.h ../pad_joycon.h ../pad_joycon_mac.h ../pad_map.h ../pad_motion.h ../steam_input.h ../pad_slots.h ../front.h ../dm_input.h ../haptics_apple.h ../hle_haptics.h ../dm.h ../motion_apple.h ../motion_pro_mac.h ../motion_pro.h $(GAME_H) | $(B)
	$(CLANG) $(GINC) -O1 -fobjc-arc -Wall -Werror -Wno-unused-function -o $@ t_controllers_mac.m -framework Cocoa -framework GameController -framework IOKit
