# The MIT License (MIT)
#
# Copyright (C) 2026 Scott Duensing
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

# src/test/basic -- host-side BASIC end-to-end tests (suite T7)
#
#   make test            headless sample/fixture runs, compaction
#                        differential, compiler fuzz, bytecode fuzz
#   make test TESTSAN=1  same with the fuzzers built under ASan/UBSan
#                        (bin/host/basrun and bascomp come from the
#                        dvxbasic Makefile, which honours TESTSAN too)
#   make fuzz COV=1      fuzzers built with gcov instrumentation under
#                        obj/hostcov (same split as the dvxbasic Makefile)
#
# Tunables: FUZZ_N (mutations per seed), FUZZ_SEED, FUZZ_STEPS.

HOSTCC      = gcc
WARNFLAGS   = -Wall -Wextra -Werror -Wno-type-limits -Wno-sign-compare -Wno-format-truncation -Wno-stringop-truncation
DVXBASIC    = ../../apps/kpunch/dvxbasic
LIBDVX      = ../../libs/kpunch/libdvx
INCLUDES    = -I$(DVXBASIC) -I$(LIBDVX) -I$(LIBDVX)/platform -I$(LIBDVX)/thirdparty
HOSTCFLAGS  = -O2 -g $(WARNFLAGS) -D_GNU_SOURCE $(INCLUDES)
ifeq ($(TESTSAN),1)
HOSTCFLAGS += -fno-omit-frame-pointer -fsanitize=address,undefined
endif

REPO        = ../../..
# COV=1: instrumented binaries and their scratch tree live under
# obj/hostcov so they never mix with the sanitized or release builds.
ifeq ($(COV),1)
HOSTCFLAGS += --coverage
HOSTDIR     = $(REPO)/obj/hostcov/basic
OUTDIR      = $(REPO)/obj/hostcov/test/basic
else
HOSTDIR     = $(REPO)/bin/host
OUTDIR      = $(REPO)/obj/test/basic
endif
CORPUS      = $(abspath $(OUTDIR)/corpus)
FUZZWORK    = $(OUTDIR)/fuzzwork
GOLDEN      = $(abspath $(REPO)/src/test/samples/golden)

FUZZ_N      = 10
FUZZ_SEED   = 12345
FUZZ_STEPS  = 200000

# Corrupt length fields legitimately ask for huge allocations; the
# deserializer must handle a NULL from malloc, so let ASan return one
# instead of aborting the run.
ASAN_ENV    = ASAN_OPTIONS=allocator_may_return_null=1:detect_leaks=1

COMPILER_SRCS = $(DVXBASIC)/compiler/lexer.c $(DVXBASIC)/compiler/parser.c $(DVXBASIC)/compiler/codegen.c $(DVXBASIC)/compiler/symtab.c
RUNTIME_SRCS  = $(DVXBASIC)/runtime/vm.c $(DVXBASIC)/runtime/values.c $(DVXBASIC)/runtime/serialize.c
SUPPORT_SRCS  = $(LIBDVX)/platform/dvxPlatformUtil.c $(LIBDVX)/thirdparty/stb_ds_impl.c
HARNESS_HDRS  = $(wildcard $(DVXBASIC)/*.h $(DVXBASIC)/compiler/*.h $(DVXBASIC)/runtime/*.h) fuzzCommon.h

FUZZ_COMPILE_SRCS = fuzzCompile.c fuzzCommon.c $(COMPILER_SRCS) $(RUNTIME_SRCS) $(SUPPORT_SRCS)
FUZZ_MODULE_SRCS  = fuzzModule.c fuzzCommon.c $(COMPILER_SRCS) $(RUNTIME_SRCS) $(LIBDVX)/dvxResource.c $(SUPPORT_SRCS)

FUZZ_COMPILE = $(abspath $(HOSTDIR)/fuzzCompile)
FUZZ_MODULE  = $(abspath $(HOSTDIR)/fuzzModule)

HARNESS_SRCS = $(DVXBASIC)/test_suite.c $(DVXBASIC)/test_compiler.c $(DVXBASIC)/test_compact.c

TESTSAN_STAMP = $(OUTDIR)/.testsan-$(if $(filter 1,$(TESTSAN)),on,off)

.PHONY: all build test corpus runs fuzz clean

all: build

build: $(FUZZ_COMPILE) $(FUZZ_MODULE)

test: runs fuzz

# basrun-driven sample / fixture / compaction runs
runs:
	./runBasic.sh

# Regenerate the seed corpus from the C harnesses every time: cheap,
# and it can never go stale.
corpus: | $(OUTDIR)
	rm -rf $(CORPUS)
	./extractCorpus.py $(CORPUS) $(HARNESS_SRCS)

# Fuzzers run inside a scratch directory so file-system statements in
# mutated programs only ever touch files there.
fuzz: build corpus
	rm -rf $(FUZZWORK)
	mkdir -p $(FUZZWORK)
	cd $(FUZZWORK) && $(ASAN_ENV) $(FUZZ_COMPILE) -n $(FUZZ_N) -seed $(FUZZ_SEED) -steps $(FUZZ_STEPS) $(CORPUS)
	cd $(FUZZWORK) && $(ASAN_ENV) $(FUZZ_MODULE) -n $(FUZZ_N) -seed $(FUZZ_SEED) -steps $(FUZZ_STEPS) -corpus $(CORPUS) $(wildcard $(GOLDEN)/*.app)

$(FUZZ_COMPILE): $(FUZZ_COMPILE_SRCS) $(HARNESS_HDRS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTCFLAGS) -o $@ $(FUZZ_COMPILE_SRCS) -lm

$(FUZZ_MODULE): $(FUZZ_MODULE_SRCS) $(HARNESS_HDRS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTCFLAGS) -o $@ $(FUZZ_MODULE_SRCS) -lm

$(TESTSAN_STAMP): | $(OUTDIR)
	rm -f $(OUTDIR)/.testsan-on $(OUTDIR)/.testsan-off
	touch $@

$(HOSTDIR) $(OUTDIR):
	mkdir -p $@

clean:
	rm -rf $(OUTDIR) $(FUZZ_COMPILE) $(FUZZ_MODULE)
