include ../../sample_objdir.mk

TARGET      := vfs_read_overflow_test
TARGET_TEST := vfs_mapped_ptr_test
TARGET_TEST2 := vfs_large_file_test
TARGET_TEST3 := vfs_seek_contract_test
TARGET_TEST4 := vfs_bulk_read_test
TARGET_TEST5 := filestream_rbuf_fault_test
TARGET_TEST6 := vfs_v5_metadata_test
TARGET_TEST7 := vfs_rename_replace_test
TARGET_TEST8 := vfs_prefetch_test
TARGET_TEST9 := vfs_readahead_test
TARGET_TEST10 := vfs_view_test
TARGET_TEST11 := vfs_prefetch_offthread_test
TARGET_TEST12 := vfs_close_result_test

LIBRETRO_COMM_DIR := ../../..

COMMON_SOURCES := \
	$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
	$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
	$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
	$(LIBRETRO_COMM_DIR)/file/file_path.c \
	$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
	$(LIBRETRO_COMM_DIR)/file/retro_dirent.c \
	$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
	$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
	$(LIBRETRO_COMM_DIR)/time/rtime.c \
	$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c

COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS        := vfs_read_overflow_test.o vfs_mapped_ptr_test.o \
	vfs_large_file_test.o vfs_seek_contract_test.o \
	vfs_bulk_read_test.o filestream_rbuf_fault_test.o \
	vfs_v5_metadata_test.o vfs_rename_replace_test.o \
	vfs_prefetch_test.o vfs_readahead_test.o vfs_view_test.o \
	vfs_prefetch_offthread_test.o vfs_close_result_test.o $(COMMON_OBJS)

# vfs_view_test also hashes through sha1_calculate(), whose dispatch
# asks features_cpu.c what the CPU has.
VIEW_OBJS := $(COMMON_OBJS) \
	$(call sample_objs,$(LIBRETRO_COMM_DIR)/hash/lrc_hash.o \
	$(LIBRETRO_COMM_DIR)/features/features_cpu.o)

# filestream_rbuf_fault_test drives the two arms on which
# filestream_rbuf_fill() gives up and hands the caller back to the
# byte-at-a-time path.  The tell arm needs nothing from the
# implementation - filestream_vfs_init() is seam enough - but a refused
# allocation has no other way in, so that one target links its own
# file_stream.o with FILESTREAM_RBUF_MALLOC pointed at its fault
# allocator.  Scoped to this object and no further: every other target
# here, and every build in the tree, links the unmodified unit with
# plain malloc() and no added symbol or branch.
FAULT_FS_OBJ := file_stream_rbuf_fault.o
FAULT_OBJS   := $(filter-out %/file_stream.o,$(COMMON_OBJS)) \
	$(FAULT_FS_OBJ)

# vfs_readahead_test needs the read-ahead thread, so that one target
# links a vfs_implementation.o built with HAVE_THREADS and the threads.
RA_VFS_OBJ := vfs_implementation_threads.o
RA_OBJS    := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
	$(RA_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o
ifeq ($(UNAME_S),Linux)
   RA_LIBS := -lpthread
endif

# vfs_prefetch_offthread_test links its own vfs_implementation.o, built
# with the read-ahead thread and VFS_WIN32_PREFETCH_HOOK pointed at the
# test's slow stand-in for PrefetchVirtualMemory. Scoped to this object:
# every other build calls the real function.
HOOK_VFS_OBJ := vfs_implementation_prefetch_hook.o
HOOK_OBJS    := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
	$(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o

# vfs_rename_replace_test runs the replace-on-rename branch the consoles
# take, so that one target links its own vfs_implementation.o built
# with -DGEKKO (and, like GameCube and Wii, no mmap), and wraps
# rename/remove/stat to refuse a rename onto an existing name as those
# filesystems do.
RENAME_VFS_OBJ := vfs_implementation_gekko.o
RENAME_OBJS    := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
	$(RENAME_VFS_OBJ)

# vfs_close_result_test runs the descriptor path PS2 takes for whole-file
# reads and writes, so that one target links its own
# vfs_implementation.o built with -DPS2 (no mmap there either), with
# every close() in it routed through the test's stand-in for ps2sdk's,
# which reports the descriptor number on success.
CLOSE_VFS_OBJ := vfs_implementation_close_hook.o
CLOSE_OBJS    := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
	$(CLOSE_VFS_OBJ)

# -DHAVE_MMAP is required for vfs_read_overflow_test to exercise the
# patched code path; without it the implementation falls back to
# buffered fread and that test becomes a smoke test that can't
# discriminate the overflow case.
#
# vfs_mapped_ptr_test needs BOTH settings to be meaningful, which is
# why this is a variable rather than a fixed flag.  With mappings on,
# filestream_matches_buf() compares against the map and its windowed
# fallback never runs - so the window-boundary cases would be
# asserting on code that was not executed.  Build with MMAP=0 to put
# that path under test; the test reports which one it took.
# Windows has no <sys/mman.h>.  The Win32 file-mapping backend is
# picked up from _WIN32 inside vfs.h and needs no flag here, so
# default this off there rather than leave a plain `make` in an MSYS2
# shell failing on a missing POSIX header.
UNAME_S := $(shell uname -s 2>/dev/null)
ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S)))
   MMAP ?= 0
else
   MMAP ?= 1
endif
ifeq ($(MMAP),1)
   CFLAGS += -DHAVE_MMAP
endif

# vfs_bulk_read_test asserts which I/O path an open lands on, and the
# interesting configuration - descriptor I/O with no file mapping - is
# the one the consoles have and no desktop does.  Build
# 'make MMAP=0 DESCRIPTOR_IO=1' to reproduce it here; MMAP=0 on its
# own gives the other case, a target that is on neither list and must
# keep its buffered behaviour unchanged.
DESCRIPTOR_IO ?=
ifeq ($(DESCRIPTOR_IO),1)
   CFLAGS += -DVFS_HAVE_DESCRIPTOR_IO=1
endif

# FAKE_CFR=1 stands in a copy_file_range that copies to EOF whatever
# len it was given (what gVisor was observed doing); FAKE_CFR=2 one
# that copies three times len.  Both exercise vfs_v5_metadata_test's
# distrust-and-resume path on a kernel that would never trip it.
ifneq ($(FAKE_CFR),)
   CFLAGS += -DVFS_COPY_TEST_FAKE_CFR=$(FAKE_CFR)
endif
# Every sample build narrates the copy stepper's platform calls on
# stderr (which path, what was asked, what came back); a frontend
# build never defines this.
CFLAGS += -DVFS_COPY_DEBUG
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -I$(LIBRETRO_COMM_DIR)/include

# The samples workflow passes SANITIZER=address,undefined to every
# sample dir; without this block the variable was silently ignored
# here, so these two ran unchecked while the rest of the tree did not.
ifneq ($(SANITIZER),)
   CFLAGS  := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
   LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif

# --wrap is a GNU ld option, and on a Windows host the _WIN32 rename
# branch would be compiled ahead of the GEKKO one, so the console
# rename target is built on Linux only.
ifeq ($(UNAME_S),Linux)
   RENAME_TARGET := $(TARGET_TEST7)
endif

all: $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) \
	$(TARGET_TEST5) $(TARGET_TEST6) $(TARGET_TEST8) $(TARGET_TEST9) \
	$(TARGET_TEST10) $(TARGET_TEST11) $(TARGET_TEST12) $(RENAME_TARGET)

%.o: %.c
	$(CC) -c -o $@ $< $(CFLAGS)

$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
	@mkdir -p $(@D)
	$(CC) -c -o $@ $< $(CFLAGS)

$(TARGET): vfs_read_overflow_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(TARGET_TEST): vfs_mapped_ptr_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(TARGET_TEST8): vfs_prefetch_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(TARGET_TEST10): vfs_view_test.o $(VIEW_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(RA_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
	$(CC) -c -o $@ $< $(CFLAGS) -DHAVE_THREADS

rthreads_ra.o: $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c
	$(CC) -c -o $@ $< $(CFLAGS) -DHAVE_THREADS

retro_eventcount_ra.o: $(LIBRETRO_COMM_DIR)/rthreads/retro_eventcount.c
	$(CC) -c -o $@ $< $(CFLAGS) -DHAVE_THREADS

$(TARGET_TEST9): vfs_readahead_test.o $(RA_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS)

$(HOOK_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c prefetch_hook.h
	$(CC) -c -o $@ $< $(CFLAGS) -DHAVE_THREADS -I. -include prefetch_hook.h \
		-DVFS_WIN32_PREFETCH_HOOK=vfs_prefetch_test_hook

$(TARGET_TEST11): vfs_prefetch_offthread_test.o $(HOOK_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS)

# vfs_large_file_test builds sparse fixtures of 3 GiB and 5 GiB.  They
# cost no space on a filesystem with sparse files and are removed
# afterwards; where the filesystem cannot hold them the test skips.
$(TARGET_TEST2): vfs_large_file_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

# vfs_seek_contract_test runs its contract checks on a 4 KiB fixture
# first, so a machine that cannot hold the 4 GiB one still tests the
# properties instead of skipping the run.
$(TARGET_TEST3): vfs_seek_contract_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

# vfs_bulk_read_test covers filestream_read_file() and the descriptor
# path RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK selects.  It spawns
# threads to check that whole-file reads hold no shared state, so it
# is the one target here that needs -lpthread; that also makes it the
# one worth running under SANITIZER=thread.
$(TARGET_TEST4): vfs_bulk_read_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS) -lpthread

$(FAULT_FS_OBJ): $(LIBRETRO_COMM_DIR)/streams/file_stream.c \
		rbuf_fault_hooks.h
	$(CC) -c -o $@ $< $(CFLAGS) -I. -include rbuf_fault_hooks.h \
		-DFILESTREAM_RBUF_MALLOC=filestream_rbuf_fault_malloc

# filestream_rbuf_fault_test installs its own VFS, which takes the
# memory mapping out of play, so it behaves identically with MMAP=1 and
# MMAP=0 and there is nothing to gain from running it in both lanes.
$(TARGET_TEST5): filestream_rbuf_fault_test.o $(FAULT_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

# vfs_v5_metadata_test covers the VFS API v5 additions (read-only
# state, mtime, begin/step/close copy, dirent_stat) through the path_*,
# filestream_copy* and retro_dirent_stat wrappers.  The copy is a
# caller-stepped state machine: no threads, no extra objects.
$(TARGET_TEST6): vfs_v5_metadata_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(RENAME_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
	$(CC) -c -o $@ $< $(filter-out -DHAVE_MMAP,$(CFLAGS)) -DGEKKO

$(TARGET_TEST7): vfs_rename_replace_test.o $(RENAME_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS) -Wl,--wrap=rename,--wrap=remove,--wrap=stat

$(CLOSE_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c close_hook.h
	$(CC) -c -o $@ $< $(filter-out -DHAVE_MMAP,$(CFLAGS)) -DPS2 \
		-I. -include close_hook.h

$(TARGET_TEST12): vfs_close_result_test.o $(CLOSE_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

clean:
	rm -rf $(SAMPLE_OBJDIR)
	rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \
		$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
		$(TARGET_TEST7) $(TARGET_TEST8) $(TARGET_TEST9) $(TARGET_TEST10) \
		$(TARGET_TEST11) $(TARGET_TEST12) $(CLOSE_VFS_OBJ) \
		$(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ) \
		$(RA_VFS_OBJ) $(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o
	rm -rf v5_meta_dir
	rm -f large_3gib.bin large_5gib.bin seek_small.bin seek_4gib.bin
	rm -f rbuf_fault.txt vfs_readahead_fixture.bin vfs_prefetch_offthread.bin
	rm -f vfs_view_fixture.bin vfs_view_update.bin vfs_view_empty.bin
	rm -f rename_replace.bin rename_replace.bin.old rename_replace.bin.tmp
	rm -f close_result.bin close_result_w.bin
	rm -f bulk_select.bin bulk_select_w.bin bulk_roundtrip.bin \
		bulk_short.bin bulk_shrank.bin bulk_error.bin \
		bulk_binary.bin bulk_threads.bin

.PHONY: clean
