diff --git a/CLAUDE.md b/CLAUDE.md index 030eb3a5..1ee388f5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,7 +109,7 @@ Active platforms (as of most recent): miyoomini, trimuismart, rg35xx, rg35xxplus ## Development Commands -### Quality Assurance (Makefile.qa) +### Quality Assurance (makefile.qa) ```bash # Quick commands (recommended) @@ -118,12 +118,12 @@ make lint # Run static analysis make format # Format code # Additional QA targets -make -f Makefile.qa docker-shell # Enter container for debugging -make -f Makefile.qa test-native # Run natively (not recommended on macOS) -make -f Makefile.qa lint-full # Lint entire workspace (verbose) -make -f Makefile.qa lint-shell # Lint shell scripts -make -f Makefile.qa format-check # Check formatting only -make -f Makefile.qa clean-tests # Clean test artifacts +make -f makefile.qa docker-shell # Enter container for debugging +make -f makefile.qa test-native # Run natively (not recommended on macOS) +make -f makefile.qa lint-full # Lint entire workspace (verbose) +make -f makefile.qa lint-shell # Lint shell scripts +make -f makefile.qa format-check # Check formatting only +make -f makefile.qa clean-tests # Clean test artifacts ``` **Note:** Tests run in Docker by default, using a Debian Buster ARM64 container that matches the platform toolchains (GCC 8.3.0). This eliminates macOS-specific build issues and ensures consistency with the actual build environment. @@ -227,7 +227,7 @@ All paths use forward slashes (`/`), even for Windows cross-compilation. Platfor - **Braces on same line** - `if (x) {` not `if (x)\n{` - **Left-aligned pointers** - `char* name` not `char *name` - **100 character line limit** -- Run `make -f Makefile.qa format` before committing +- Run `make -f makefile.qa format` before committing See `.clang-format` for complete style definition. @@ -257,7 +257,7 @@ See `.clang-format` for complete style definition. | Common definitions | `workspace/all/common/defines.h` | | Test suite | `tests/unit/all/common/test_utils.c` | | Build orchestration | `Makefile` (host-side) | -| QA tools | `Makefile.qa` | +| QA tools | `makefile.qa` | ## Documentation @@ -268,11 +268,36 @@ See `.clang-format` for complete style definition. ## Current Test Coverage -``` -✅ workspace/all/common/utils.c - 52 tests (100% coverage) -⏳ workspace/all/common/api.c - TODO -⏳ workspace/all/minui/minui.c - TODO (needs SDL mocks) -⏳ workspace/all/minarch/minarch.c - TODO (integration tests) -``` +**Total: 342 tests across 15 test suites** ✅ + +### Extracted and Tested Modules + +To enable comprehensive testing, complex logic has been extracted from large files into focused, testable modules: + +| Module | Tests | Extracted From | Purpose | +|--------|-------|----------------|---------| +| utils.c (split into 6 modules) | 100 | (original) | String, file, name, date, math utilities | +| pad.c | 21 | api.c | Button state machine, analog input | +| collections.c | 30 | minui.c | Array, Hash data structures | +| gfx_text.c | 32 | api.c | Text truncation, wrapping, sizing | +| audio_resampler.c | 18 | api.c | Bresenham sample rate conversion | +| minarch_paths.c | 16 | minarch.c | Save file path generation | +| minui_utils.c | 17 | minui.c | Index char, console dir detection | +| m3u_parser.c | 20 | minui.c | M3U playlist parsing | +| minui_file_utils.c | 25 | minui.c | File/dir checking utilities | +| map_parser.c | 22 | minui.c/minarch.c | ROM display name aliasing | +| collection_parser.c | 11 | minui.c | Custom ROM list parsing | +| recent_file.c | 18 | minui.c | Recent games read/write | +| binary_file_utils.c | 12 | minarch.c | Binary file read/write | + +### Testing Technologies + +- **fff (Fake Function Framework)** - Header-only library for mocking SDL functions +- **GCC --wrap** - Link-time file system function mocking (read operations) +- **Real temp files** - For write operations and binary I/O (mkstemp, mkdtemp) +- **Unity** - Test framework with assertions and test runner +- **Docker** - Debian Buster ARM64 container matching platform toolchains (GCC 8.3.0) + +All tests run in Docker by default to ensure consistency with the cross-compilation environment. -See `docs/testing-checklist.md` for detailed testing plan. +See `tests/README.md` for comprehensive testing guide and examples. diff --git a/Makefile.qa b/Makefile.qa deleted file mode 100644 index 05d79a04..00000000 --- a/Makefile.qa +++ /dev/null @@ -1,206 +0,0 @@ -# Quality Assurance Makefile -# Run static analysis and tests - -.PHONY: help lint lint-code lint-full lint-shell test test-native format format-check clean-qa docker-build docker-test docker-shell - -help: - @echo "MinUI Quality Assurance Tools" - @echo "" - @echo "Main targets (use these):" - @echo " make test - Run unit tests (Docker, recommended)" - @echo " make lint - Run ALL linting checks (cppcheck, format-check, shellcheck)" - @echo " make format - Format code with clang-format (MODIFIES FILES)" - @echo "" - @echo "Individual lint targets:" - @echo " make lint-code - Run cppcheck on workspace/all/" - @echo " make lint-full - Run cppcheck on entire workspace (verbose)" - @echo " make format-check - Check if code is formatted (no changes)" - @echo " make lint-shell - Run shellcheck on shell scripts" - @echo "" - @echo "Docker targets:" - @echo " make docker-test - Run tests in Debian Buster container" - @echo " make docker-build - Build test Docker image" - @echo " make docker-shell - Enter Docker container for debugging" - @echo "" - @echo "Other:" - @echo " make test-native - Run tests natively (not recommended on macOS)" - @echo " make clean-qa - Clean QA artifacts" - @echo "" - @echo "Installing tools:" - @echo " macOS: brew install cppcheck llvm shellcheck" - @echo " Linux: sudo apt-get install cppcheck clang-format shellcheck" - -# Check for critical issues: warnings, performance, style -# workspace/all/ is fully cleaned - zero issues -# Device-specific code has targeted style suppressions -CPPCHECK_FLAGS = --enable=warning \ - --suppressions-list=.cppcheck-suppressions \ - --inline-suppr \ - --error-exitcode=0 \ - --quiet - -# Check if cppcheck is installed -check-cppcheck: - @which cppcheck > /dev/null || (echo "Error: cppcheck not installed. Run 'brew install cppcheck' or 'apt-get install cppcheck'" && exit 1) - -check-shellcheck: - @which shellcheck > /dev/null || (echo "Error: shellcheck not installed. Run 'brew install shellcheck' or 'apt-get install shellcheck'" && exit 1) - -# Main lint target - runs all linting checks -lint: check-cppcheck check-clang-format check-shellcheck - @echo "=================================" && \ - echo "Running ALL linting checks..." && \ - echo "=================================" && \ - echo "" && \ - $(MAKE) -f Makefile.qa lint-code && \ - echo "" && \ - $(MAKE) -f Makefile.qa format-check && \ - echo "" && \ - $(MAKE) -f Makefile.qa lint-shell && \ - echo "" && \ - echo "=================================" && \ - echo "✓ All linting checks passed" && \ - echo "=================================" - -# Lint common code (most important, least platform-specific) -lint-code: check-cppcheck - @echo "Running cppcheck on workspace/all/ (common code)..." - @echo "Checking for: NULL dereferences, memory leaks, buffer overflows, uninitialized variables" - @echo "" - cppcheck $(CPPCHECK_FLAGS) \ - -I workspace/all/common \ - workspace/all/minui/ \ - workspace/all/minarch/ \ - workspace/all/common/ \ - workspace/all/clock/ \ - workspace/all/minput/ \ - workspace/all/say/ \ - workspace/all/syncsettings/ - @echo "" - @echo "✓ Static analysis complete" - -# More thorough check (all workspace code) -lint-full: check-cppcheck - @echo "Running cppcheck on entire workspace..." - cppcheck $(CPPCHECK_FLAGS) \ - -I workspace/all/common \ - workspace/ - -# Test configuration -TEST_CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-parameter -TEST_INCLUDES = -I tests/support -I tests/support/unity -I workspace/all/common -TEST_UNITY = tests/support/unity/unity.c - -# List of test executables -TEST_EXECUTABLES = tests/utils_test - -# Default test target: use Docker -test: docker-test - -# Native test target (for Linux or if Docker unavailable) -test-native: $(TEST_EXECUTABLES) - @echo "Running unit tests (native)..." - @for test in $(TEST_EXECUTABLES); do \ - ./$$test; \ - echo ""; \ - done - @echo "✓ All tests passed" - -# Build comprehensive utils tests -tests/utils_test: tests/unit/all/common/test_utils.c workspace/all/common/utils.c $(TEST_UNITY) - @echo "Building comprehensive utils tests..." - @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) - -clean-tests: - rm -f $(TEST_EXECUTABLES) tests/*.o tests/**/*.o - -# Code formatting with clang-format -CLANG_FORMAT = clang-format -FORMAT_PATHS = workspace/all/minui/*.c \ - workspace/all/minarch/*.c \ - workspace/all/common/*.c \ - workspace/all/common/*.h \ - workspace/all/clock/*.c \ - workspace/all/minput/*.c \ - workspace/all/say/*.c \ - workspace/all/syncsettings/*.c - -check-clang-format: - @which $(CLANG_FORMAT) > /dev/null || (echo "Error: clang-format not installed. Run 'brew install llvm' or 'apt-get install clang-format'" && exit 1) - -# Format code in-place (MODIFIES FILES) -format: check-clang-format - @echo "Formatting code with clang-format..." - @$(CLANG_FORMAT) -i $(FORMAT_PATHS) - @echo "✓ Code formatted" - @echo "" - @echo "NOTE: Files were modified. Review changes with 'git diff'" - -# Check if code is formatted (no modifications) -format-check: check-clang-format - @echo "Checking code formatting..." - @$(CLANG_FORMAT) --dry-run -Werror $(FORMAT_PATHS) 2>&1 || \ - (echo "✗ Code is not formatted. Run 'make -f Makefile.qa format' to fix." && exit 1) - @echo "✓ Code is properly formatted" - -# Shell script linting with shellcheck -# Exclude cores, toolchains, and scripts with embedded binary data -SHELL_SCRIPTS = $(shell find skeleton workspace -name "*.sh" -type f \ - -not -path "*/cores/*" \ - -not -path "*/toolchains/*" \ - -not -path "*/em_ui.sh" \ - 2>/dev/null) -ROOT_SCRIPTS = commits.sh - -lint-shell: check-shellcheck - @echo "Running shellcheck on shell scripts..." - @echo "Checking $(shell echo $(SHELL_SCRIPTS) $(ROOT_SCRIPTS) | wc -w | tr -d ' ') scripts (forgiving rules)" - @echo "" - @ERROR_COUNT=0; \ - for script in $(ROOT_SCRIPTS) $(SHELL_SCRIPTS); do \ - if [ -f "$$script" ]; then \ - shellcheck "$$script" || ERROR_COUNT=$$((ERROR_COUNT + 1)); \ - fi; \ - done; \ - if [ $$ERROR_COUNT -gt 0 ]; then \ - echo ""; \ - echo "Found issues in $$ERROR_COUNT script(s)"; \ - echo "Note: Using forgiving rules (see .shellcheckrc)"; \ - else \ - echo "✓ All shell scripts passed"; \ - fi - -clean-qa: - @echo "Cleaning QA artifacts..." - rm -f cppcheck-report.txt - -# Generate a report file -report: check-cppcheck - @echo "Generating static analysis report..." - cppcheck $(CPPCHECK_FLAGS) --template="{file}:{line}: {severity}: {message}" \ - -I workspace/all/common \ - workspace/all/ > cppcheck-report.txt 2>&1 || true - @echo "Report saved to cppcheck-report.txt" - @wc -l cppcheck-report.txt - -# Docker-based testing targets -DOCKER_IMAGE = minui-test -DOCKER_RUN = docker run --rm -v $(shell pwd):/minui -w /minui $(DOCKER_IMAGE) - -docker-build: - @echo "Building test Docker image (Debian Buster)..." - docker build -t $(DOCKER_IMAGE) -f tests/Dockerfile . - @echo "✓ Docker image ready" - -docker-test: docker-build - @echo "Running tests in Docker container (Debian Buster)..." - @echo "" - $(DOCKER_RUN) make -f Makefile.qa clean-tests test-native - -docker-lint: docker-build - @echo "Running linting in Docker container..." - $(DOCKER_RUN) make -f Makefile.qa lint - -docker-shell: docker-build - @echo "Entering Docker container shell..." - docker run --rm -it -v $(shell pwd):/minui -w /minui $(DOCKER_IMAGE) /bin/bash diff --git a/makefile b/makefile index 763fb1eb..33897bd2 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,22 @@ -# MinUI - -# NOTE: this runs on the host system (eg. macOS) not in a docker image -# it has to, otherwise we'd be running a docker in a docker and oof +# MinUI Build System +# Main makefile for orchestrating multi-platform builds +# +# This makefile runs on the HOST system (macOS/Linux), not in Docker. +# It manages Docker-based cross-compilation for multiple ARM platforms. +# +# Common targets: +# make shell PLATFORM= - Enter platform's build environment +# make test - Run unit tests (uses Docker) +# make lint - Run static analysis +# make format - Format code with clang-format +# make all - Build all platforms (creates release ZIPs) +# +# Platform-specific build: +# make build PLATFORM= - Build binaries for specific platform +# make system PLATFORM= - Copy binaries to build directory +# +# See makefile.qa for quality assurance targets +# See makefile.toolchain for Docker/cross-compilation setup # prevent accidentally triggering a full build with invalid calls ifneq (,$(PLATFORM)) @@ -10,11 +25,13 @@ $(error found PLATFORM arg but no target, did you mean "make PLATFORM=$(PLATFORM endif endif +# Default platforms to build (can be overridden with PLATFORMS=...) ifeq (,$(PLATFORMS)) PLATFORMS = miyoomini trimuismart rg35xx rg35xxplus my355 tg5040 zero28 rgb30 m17 gkdpixel my282 magicmini endif ########################################################### +# Release versioning BUILD_HASH:=$(shell git rev-parse --short HEAD) RELEASE_TIME:=$(shell TZ=GMT date +%Y%m%d) @@ -24,34 +41,40 @@ RELEASE_DOT:=$(shell find -E ./releases/. -regex ".*/${RELEASE_BASE}-[0-9]+-base RELEASE_NAME=$(RELEASE_BASE)-$(RELEASE_DOT) ########################################################### +# Build configuration -.PHONY: build test lint format +.PHONY: build test lint format all shell name clean setup done export MAKEFLAGS=--no-print-directory +# Build everything: all platforms, create release ZIPs all: setup $(PLATFORMS) special package done +# Enter Docker build environment for a specific platform shell: make -f makefile.toolchain PLATFORM=$(PLATFORM) +# Print release name (useful for CI/scripts) name: @echo $(RELEASE_NAME) -# QA convenience targets (forward to Makefile.qa) +# QA convenience targets (forward to makefile.qa) test: - @make -f Makefile.qa test + @make -f makefile.qa test lint: - @make -f Makefile.qa lint + @make -f makefile.qa lint format: - @make -f Makefile.qa format + @make -f makefile.qa format +# Build all components for a specific platform (in Docker) build: # ---------------------------------------------------- make build -f makefile.toolchain PLATFORM=$(PLATFORM) # ---------------------------------------------------- +# Copy platform binaries to build directory system: make -f ./workspace/$(PLATFORM)/platform/makefile.copy PLATFORM=$(PLATFORM) @@ -65,7 +88,9 @@ system: cp ./workspace/all/clock/build/$(PLATFORM)/clock.elf ./build/EXTRAS/Tools/$(PLATFORM)/Clock.pak/ cp ./workspace/all/minput/build/$(PLATFORM)/minput.elf ./build/EXTRAS/Tools/$(PLATFORM)/Input.pak/ -cores: # TODO: can't assume every platform will have the same stock cores (platform should be responsible for copy too) +# Copy libretro cores to build directory +# TODO: can't assume every platform will have the same stock cores (platform should be responsible for copy too) +cores: # stock cores cp ./workspace/$(PLATFORM)/cores/output/fceumm_libretro.so ./build/SYSTEM/$(PLATFORM)/cores cp ./workspace/$(PLATFORM)/cores/output/gambatte_libretro.so ./build/SYSTEM/$(PLATFORM)/cores @@ -93,17 +118,20 @@ ifneq ($(PLATFORM),gkdpixel) cp ./workspace/$(PLATFORM)/cores/output/mednafen_vb_libretro.so ./build/EXTRAS/Emus/$(PLATFORM)/VB.pak endif +# Build everything for a platform: binaries, system files, cores common: build system cores - + +# Remove build artifacts clean: rm -rf ./build +# Prepare fresh build directory and skeleton setup: name # ---------------------------------------------------- - # make sure we're running in an input device + # Make sure we're running in an interactive terminal (not piped/redirected) tty -s - # ready fresh build + # Create fresh build directory rm -rf ./build mkdir -p ./releases cp -R ./skeleton ./build @@ -113,14 +141,16 @@ setup: name cd ./build && find . -type f -name '*.meta' -delete echo $(BUILD_HASH) > ./workspace/hash.txt - # copy readmes to workspace so we can use Linux fmt instead of host's + # Copy READMEs to workspace for formatting (uses Linux fmt in Docker) mkdir -p ./workspace/readmes cp ./skeleton/BASE/README.md ./workspace/readmes/BASE-in.txt cp ./skeleton/EXTRAS/README.md ./workspace/readmes/EXTRAS-in.txt +# Signal build completion (macOS only - harmless on Linux) done: say "done" 2>/dev/null || true +# Platform-specific packaging for Miyoo/Trimui family special: # setup miyoomini/trimui/magicx family .tmp_update in BOOT mv ./build/BOOT/common ./build/BOOT/.tmp_update @@ -138,9 +168,10 @@ ifneq (,$(findstring my355, $(PLATFORMS))) cp -r ./workspace/my355/other/squashfs/output/* ./build/BASE/miyoo355/app/my355/payload/ endif +# Backward compatibility for platforms that were merged tidy: # ---------------------------------------------------- - # copy update from merged platform to old pre-merge platform bin so old cards update properly + # Copy update scripts to old platform directories for smooth upgrades ifneq (,$(findstring rg35xxplus, $(PLATFORMS))) mkdir -p ./build/SYSTEM/rg40xxcube/bin/ cp ./build/SYSTEM/rg35xxplus/bin/install.sh ./build/SYSTEM/rg40xxcube/bin/ @@ -150,11 +181,12 @@ ifneq (,$(findstring tg5040, $(PLATFORMS))) cp ./build/SYSTEM/tg5040/bin/install.sh ./build/SYSTEM/tg3040/paks/MinUI.pak/launch.sh endif +# Create final release ZIP files package: tidy # ---------------------------------------------------- - # zip up build + # Package everything into distributable ZIPs - # move formatted readmes from workspace to build + # Move formatted READMEs from workspace to build cp ./workspace/readmes/BASE-out.txt ./build/BASE/README.txt cp ./workspace/readmes/EXTRAS-out.txt ./build/EXTRAS/README.txt rm -rf ./workspace/readmes @@ -175,7 +207,9 @@ package: tidy echo "$(RELEASE_NAME)" > ./build/latest.txt ########################################################### +# Dynamic platform targets +# Match any platform name and build it .DEFAULT: # ---------------------------------------------------- # $@ diff --git a/makefile.qa b/makefile.qa new file mode 100644 index 00000000..db03b2de --- /dev/null +++ b/makefile.qa @@ -0,0 +1,313 @@ +# MinUI Quality Assurance +# Static analysis, testing, and code formatting +# +# This makefile provides quality assurance tools for the MinUI codebase: +# - Unit tests (Docker-based, using Debian Buster + GCC 8.3.0) +# - Static analysis (cppcheck for C code, shellcheck for scripts) +# - Code formatting (clang-format with project style) +# +# Quick start: +# make test - Run all unit tests (recommended) +# make lint - Run all linting checks +# make format - Auto-format code (modifies files) +# +# The main makefile forwards to this file for test/lint/format targets. +# Run 'make -f makefile.qa help' for complete target list. + +.PHONY: help lint lint-code lint-full lint-shell test test-native format format-check clean-qa docker-build docker-test docker-shell + +help: + @echo "MinUI Quality Assurance Tools" + @echo "" + @echo "Main targets (use these):" + @echo " make test - Run unit tests (Docker, recommended)" + @echo " make lint - Run ALL linting checks (cppcheck, format-check, shellcheck)" + @echo " make format - Format code with clang-format (MODIFIES FILES)" + @echo "" + @echo "Individual lint targets:" + @echo " make lint-code - Run cppcheck on workspace/all/" + @echo " make lint-full - Run cppcheck on entire workspace (verbose)" + @echo " make format-check - Check if code is formatted (no changes)" + @echo " make lint-shell - Run shellcheck on shell scripts" + @echo "" + @echo "Docker targets:" + @echo " make docker-test - Run tests in Debian Buster container" + @echo " make docker-build - Build test Docker image" + @echo " make docker-shell - Enter Docker container for debugging" + @echo "" + @echo "Other:" + @echo " make test-native - Run tests natively (not recommended on macOS)" + @echo " make clean-qa - Clean QA artifacts" + @echo "" + @echo "Installing tools:" + @echo " macOS: brew install cppcheck llvm shellcheck" + @echo " Linux: sudo apt-get install cppcheck clang-format shellcheck" + +# cppcheck configuration +# Checks for: warnings, performance issues, style problems +# workspace/all/ has zero issues (fully cleaned) +# Platform-specific code uses targeted suppressions +CPPCHECK_FLAGS = --enable=warning \ + --suppressions-list=.cppcheck-suppressions \ + --inline-suppr \ + --error-exitcode=0 \ + --quiet + +# Check if cppcheck is installed +check-cppcheck: + @which cppcheck > /dev/null || (echo "Error: cppcheck not installed. Run 'brew install cppcheck' or 'apt-get install cppcheck'" && exit 1) + +check-shellcheck: + @which shellcheck > /dev/null || (echo "Error: shellcheck not installed. Run 'brew install shellcheck' or 'apt-get install shellcheck'" && exit 1) + +# Run all linting checks (code, format, shell scripts) +lint: check-cppcheck check-clang-format check-shellcheck + @echo "=================================" && \ + echo "Running ALL linting checks..." && \ + echo "=================================" && \ + echo "" && \ + $(MAKE) -f makefile.qa lint-code && \ + echo "" && \ + $(MAKE) -f makefile.qa format-check && \ + echo "" && \ + $(MAKE) -f makefile.qa lint-shell && \ + echo "" && \ + echo "=================================" && \ + echo "✓ All linting checks passed" && \ + echo "=================================" + +# Run cppcheck on common code (platform-independent, highest priority) +lint-code: check-cppcheck + @echo "Running cppcheck on workspace/all/ (common code)..." + @echo "Checking for: NULL dereferences, memory leaks, buffer overflows, uninitialized variables" + @echo "" + cppcheck $(CPPCHECK_FLAGS) \ + -I workspace/all/common \ + workspace/all/minui/ \ + workspace/all/minarch/ \ + workspace/all/common/ \ + workspace/all/clock/ \ + workspace/all/minput/ \ + workspace/all/say/ \ + workspace/all/syncsettings/ + @echo "" + @echo "✓ Static analysis complete" + +# Run cppcheck on entire workspace (includes platform-specific code) +lint-full: check-cppcheck + @echo "Running cppcheck on entire workspace..." + cppcheck $(CPPCHECK_FLAGS) \ + -I workspace/all/common \ + workspace/ + +########################################################### +# Test Configuration +TEST_CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-parameter +TEST_INCLUDES = -I tests/support -I tests/support/unity -I workspace/all/common +TEST_UNITY = tests/support/unity/unity.c + +# All test executables (built from tests/unit/ and tests/integration/) +TEST_EXECUTABLES = tests/utils_test tests/pad_test tests/collections_test tests/gfx_text_test tests/audio_resampler_test tests/minarch_paths_test tests/minui_utils_test tests/m3u_parser_test tests/minui_file_utils_test tests/map_parser_test tests/collection_parser_test tests/recent_parser_test tests/recent_writer_test tests/directory_utils_test tests/binary_file_utils_test tests/integration_workflows_test + +# Default test target: use Docker +test: docker-test + +# Native test target (for Linux or if Docker unavailable) +test-native: $(TEST_EXECUTABLES) + @echo "Running unit tests (native)..." + @for test in $(TEST_EXECUTABLES); do \ + ./$$test; \ + echo ""; \ + done + @echo "✓ All tests passed" + +# Build comprehensive utils tests +tests/utils_test: tests/unit/all/common/test_utils.c workspace/all/common/utils.c $(TEST_UNITY) + @echo "Building comprehensive utils tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) + +# Build PAD (input) tests +tests/pad_test: tests/unit/all/common/test_api_pad.c workspace/all/common/pad.c $(TEST_UNITY) + @echo "Building PAD input tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) + +# Build collections (Array/Hash) tests +tests/collections_test: tests/unit/all/common/test_collections.c workspace/all/common/collections.c workspace/all/common/utils.c $(TEST_UNITY) + @echo "Building collections tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L + +# Build GFX text utility tests (uses fff for TTF mocking) +tests/gfx_text_test: tests/unit/all/common/test_gfx_text.c workspace/all/common/gfx_text.c workspace/all/common/utils.c tests/support/sdl_fakes.c $(TEST_UNITY) + @echo "Building GFX text utility tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) -I tests/support/fff $(TEST_CFLAGS) -DUNIT_TEST_BUILD + +# Build audio resampler tests (pure algorithm, no mocking needed) +tests/audio_resampler_test: tests/unit/all/common/test_audio_resampler.c workspace/all/common/audio_resampler.c $(TEST_UNITY) + @echo "Building audio resampler tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) + +# Build MinArch path generation tests (pure sprintf logic) +tests/minarch_paths_test: tests/unit/all/common/test_minarch_paths.c workspace/all/common/minarch_paths.c $(TEST_UNITY) + @echo "Building MinArch path generation tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) + +# Build MinUI launcher utility tests (pure string logic) +tests/minui_utils_test: tests/unit/all/common/test_minui_utils.c workspace/all/common/minui_utils.c workspace/all/common/utils.c $(TEST_UNITY) + @echo "Building MinUI launcher utility tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) + +# Build M3U parser tests (uses file mocking with GCC --wrap, Docker-only) +tests/m3u_parser_test: tests/unit/all/common/test_m3u_parser.c workspace/all/common/m3u_parser.c workspace/all/common/utils.c tests/support/fs_mocks.c $(TEST_UNITY) + @echo "Building M3U parser tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets + +# Build MinUI file utility tests (uses file mocking with GCC --wrap, Docker-only) +tests/minui_file_utils_test: tests/unit/all/common/test_minui_file_utils.c workspace/all/common/minui_file_utils.c workspace/all/common/utils.c tests/support/fs_mocks.c $(TEST_UNITY) + @echo "Building MinUI file utility tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets + +# Build map.txt parser tests (uses file mocking with GCC --wrap, Docker-only) +tests/map_parser_test: tests/unit/all/common/test_map_parser.c workspace/all/common/map_parser.c workspace/all/common/utils.c tests/support/fs_mocks.c $(TEST_UNITY) + @echo "Building map.txt parser tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets + +# Build collection parser tests (uses file mocking with GCC --wrap, Docker-only) +tests/collection_parser_test: tests/unit/all/common/test_collection_parser.c workspace/all/common/collection_parser.c workspace/all/common/utils.c tests/support/fs_mocks.c $(TEST_UNITY) + @echo "Building collection parser tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets + +# Build recent.txt file tests (uses file mocking with GCC --wrap, Docker-only) +tests/recent_parser_test: tests/unit/all/common/test_recent_parser.c workspace/all/common/recent_file.c workspace/all/common/utils.c tests/support/fs_mocks.c $(TEST_UNITY) + @echo "Building recent.txt parser tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets + +# Build recent.txt writer tests (uses real temp files, no --wrap needed) +tests/recent_writer_test: tests/unit/all/common/test_recent_writer.c workspace/all/common/recent_file.c workspace/all/common/utils.c $(TEST_UNITY) + @echo "Building recent.txt writer tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L + +# Build directory utility tests (uses real temp directories, no --wrap needed) +tests/directory_utils_test: tests/unit/all/common/test_directory_utils.c workspace/all/common/minui_file_utils.c workspace/all/common/utils.c $(TEST_UNITY) + @echo "Building directory utility tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE + +# Build binary file I/O tests (uses real temp files, no --wrap needed) +tests/binary_file_utils_test: tests/unit/all/common/test_binary_file_utils.c workspace/all/common/binary_file_utils.c $(TEST_UNITY) + @echo "Building binary file I/O tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE + +# Build integration tests (tests multiple components working together with real file I/O) +tests/integration_workflows_test: tests/integration/test_workflows.c \ + tests/integration/integration_support.c \ + workspace/all/common/m3u_parser.c \ + workspace/all/common/map_parser.c \ + workspace/all/common/collection_parser.c \ + workspace/all/common/recent_file.c \ + workspace/all/common/minui_file_utils.c \ + workspace/all/common/binary_file_utils.c \ + workspace/all/common/minarch_paths.c \ + workspace/all/common/utils.c \ + $(TEST_UNITY) + @echo "Building integration tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) -I tests/integration $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE + +clean-tests: + rm -f $(TEST_EXECUTABLES) tests/*.o tests/**/*.o tests/integration/*.o + +########################################################### +# Code Formatting +CLANG_FORMAT = clang-format +FORMAT_PATHS = workspace/all/minui/*.c \ + workspace/all/minarch/*.c \ + workspace/all/common/*.c \ + workspace/all/common/*.h \ + workspace/all/clock/*.c \ + workspace/all/minput/*.c \ + workspace/all/say/*.c \ + workspace/all/syncsettings/*.c + +check-clang-format: + @which $(CLANG_FORMAT) > /dev/null || (echo "Error: clang-format not installed. Run 'brew install llvm' or 'apt-get install clang-format'" && exit 1) + +# Format code in-place (MODIFIES FILES) +format: check-clang-format + @echo "Formatting code with clang-format..." + @$(CLANG_FORMAT) -i $(FORMAT_PATHS) + @echo "✓ Code formatted" + @echo "" + @echo "NOTE: Files were modified. Review changes with 'git diff'" + +# Check if code is formatted (no modifications) +format-check: check-clang-format + @echo "Checking code formatting..." + @$(CLANG_FORMAT) --dry-run -Werror $(FORMAT_PATHS) 2>&1 || \ + (echo "✗ Code is not formatted. Run 'make -f makefile.qa format' to fix." && exit 1) + @echo "✓ Code is properly formatted" + +########################################################### +# Shell Script Linting + +# Find all shell scripts (exclude cores, toolchains, embedded binaries) +SHELL_SCRIPTS = $(shell find skeleton workspace -name "*.sh" -type f \ + -not -path "*/cores/*" \ + -not -path "*/toolchains/*" \ + -not -path "*/em_ui.sh" \ + 2>/dev/null) +ROOT_SCRIPTS = commits.sh + +lint-shell: check-shellcheck + @echo "Running shellcheck on shell scripts..." + @echo "Checking $(shell echo $(SHELL_SCRIPTS) $(ROOT_SCRIPTS) | wc -w | tr -d ' ') scripts (forgiving rules)" + @echo "" + @ERROR_COUNT=0; \ + for script in $(ROOT_SCRIPTS) $(SHELL_SCRIPTS); do \ + if [ -f "$$script" ]; then \ + shellcheck "$$script" || ERROR_COUNT=$$((ERROR_COUNT + 1)); \ + fi; \ + done; \ + if [ $$ERROR_COUNT -gt 0 ]; then \ + echo ""; \ + echo "Found issues in $$ERROR_COUNT script(s)"; \ + echo "Note: Using forgiving rules (see .shellcheckrc)"; \ + else \ + echo "✓ All shell scripts passed"; \ + fi + +########################################################### +# Reporting and Cleanup + +clean-qa: + @echo "Cleaning QA artifacts..." + rm -f cppcheck-report.txt + +# Generate static analysis report file +report: check-cppcheck + @echo "Generating static analysis report..." + cppcheck $(CPPCHECK_FLAGS) --template="{file}:{line}: {severity}: {message}" \ + -I workspace/all/common \ + workspace/all/ > cppcheck-report.txt 2>&1 || true + @echo "Report saved to cppcheck-report.txt" + @wc -l cppcheck-report.txt + +########################################################### +# Docker Testing (recommended for consistency) +DOCKER_IMAGE = minui-test +DOCKER_RUN = docker run --rm -v $(shell pwd):/minui -w /minui $(DOCKER_IMAGE) + +docker-build: + @echo "Building test Docker image (Debian Buster)..." + docker build -t $(DOCKER_IMAGE) -f tests/Dockerfile . + @echo "✓ Docker image ready" + +docker-test: docker-build + @echo "Running tests in Docker container (Debian Buster)..." + @echo "" + $(DOCKER_RUN) make -f makefile.qa clean-tests test-native + +docker-lint: docker-build + @echo "Running linting in Docker container..." + $(DOCKER_RUN) make -f makefile.qa lint + +docker-shell: docker-build + @echo "Entering Docker container shell..." + docker run --rm -it -v $(shell pwd):/minui -w /minui $(DOCKER_IMAGE) /bin/bash diff --git a/makefile.toolchain b/makefile.toolchain index c6a2fb82..47d0deff 100644 --- a/makefile.toolchain +++ b/makefile.toolchain @@ -1,27 +1,48 @@ -# there is no reason to use this makefile manually - -.PHONY: build +# MinUI Docker Toolchain Manager +# Manages platform-specific cross-compilation toolchains +# +# This makefile is called by the main makefile to: +# 1. Clone platform-specific toolchain repositories (from union-* repos) +# 2. Build Docker images with ARM cross-compilers +# 3. Launch Docker containers for building MinUI components +# +# Toolchains are cloned to: toolchains/-toolchain/ +# Each toolchain contains a Dockerfile with the cross-compiler setup. +# +# Usage (typically called from main makefile): +# make -f makefile.toolchain PLATFORM=miyoomini # Enter shell +# make -f makefile.toolchain PLATFORM=miyoomini build # Build all +# +# DO NOT call this makefile directly unless debugging toolchain issues. +# Use the main makefile's 'shell' and 'build' targets instead. + +.PHONY: build all clean ifeq (,$(PLATFORM)) -$(error please specify PLATFORM, eg. make PLATFORM=trimui) +$(error PLATFORM not specified. Use: make -f makefile.toolchain PLATFORM=) endif +# Workspace paths (host and Docker container) HOST_WORKSPACE=$(shell pwd)/workspace GUEST_WORKSPACE=/root/workspace +# Toolchain paths GIT_IF_NECESSARY=toolchains/$(PLATFORM)-toolchain INIT_IF_NECESSARY=toolchains/$(PLATFORM)-toolchain/.build +# Default target: ensure toolchain is built, then launch interactive shell all: $(INIT_IF_NECESSARY) docker run --rm -v $(HOST_WORKSPACE):$(GUEST_WORKSPACE) $(PLATFORM)-toolchain /bin/bash +# Build Docker image (if not already built) $(INIT_IF_NECESSARY): $(GIT_IF_NECESSARY) cd toolchains/$(PLATFORM)-toolchain && make .build +# Clone toolchain repository (if not already cloned) $(GIT_IF_NECESSARY): mkdir -p toolchains git clone https://github.com/shauninman/union-$(PLATFORM)-toolchain/ toolchains/$(PLATFORM)-toolchain - @echo "Patching Dockerfile for archived Debian versions..." + @echo "Patching Dockerfile for archived Debian repositories..." @if [ -f toolchains/$(PLATFORM)-toolchain/Dockerfile ]; then \ DOCKERFILE=toolchains/$(PLATFORM)-toolchain/Dockerfile; \ if grep -q "^FROM debian:buster" $$DOCKERFILE; then \ @@ -33,8 +54,10 @@ $(GIT_IF_NECESSARY): fi; \ fi +# Clean Docker image (toolchain repo remains) clean: cd toolchains/$(PLATFORM)-toolchain && make clean +# Build all workspace components in Docker (non-interactive) build: $(INIT_IF_NECESSARY) docker run --rm -v $(HOST_WORKSPACE):$(GUEST_WORKSPACE) $(PLATFORM)-toolchain /bin/bash -c '. ~/.bashrc && cd /root/workspace && make' \ No newline at end of file diff --git a/tests/Dockerfile b/tests/Dockerfile index 49100559..b810912e 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -24,4 +24,4 @@ RUN apt-get update && apt-get install -y \ WORKDIR /minui # Default command shows available test targets -CMD ["make", "-f", "Makefile.qa", "help"] +CMD ["make", "-f", "makefile.qa", "help"] diff --git a/tests/README.md b/tests/README.md index b3b566c6..1af4cc3d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,10 +2,12 @@ This directory contains the test suite for MinUI, organized to mirror the source code structure. +**Current Status: 364 tests, all passing** ✅ + ## Quick Start ```bash -make test # Run all tests in Docker (recommended) +make test # Run all 364 tests in Docker (recommended) ``` Tests run in a Debian Buster ARM64 container that matches the platform toolchains exactly. This ensures consistency across development environments and catches platform-specific issues. @@ -29,18 +31,37 @@ Tests run in a Debian Buster ARM64 container that matches the platform toolchain ``` tests/ -├── unit/ # Unit tests (mirror workspace/ structure) +├── unit/ # Unit tests (mirror workspace/ structure) │ └── all/ │ └── common/ -│ ├── test_utils.c # Tests for workspace/all/common/utils.c -│ └── test_date_utils.c # Tests for workspace/all/common/date_utils.c -├── integration/ # Integration tests (end-to-end tests) -├── fixtures/ # Test data, sample ROMs, configs -├── support/ # Test infrastructure -│ ├── unity/ # Unity test framework -│ └── platform.h # Platform stubs for testing -├── Dockerfile # Test environment (Debian Buster ARM64) -└── README.md # This file +│ ├── test_utils.c # Utils (string, file, name, date, math) - 100 tests +│ ├── test_api_pad.c # Input state machine - 21 tests +│ ├── test_collections.c # Array/Hash data structures - 30 tests +│ ├── test_gfx_text.c # Text truncation/wrapping - 32 tests +│ ├── test_audio_resampler.c # Audio resampling - 18 tests +│ ├── test_minarch_paths.c # Save file paths - 16 tests +│ ├── test_minui_utils.c # Launcher helpers - 17 tests +│ ├── test_m3u_parser.c # M3U parsing - 20 tests +│ ├── test_minui_file_utils.c # File/dir checking - 25 tests +│ ├── test_map_parser.c # map.txt aliasing - 22 tests +│ ├── test_collection_parser.c # Collection lists - 11 tests +│ ├── test_recent_file.c # Recent games parsing - 13 tests +│ ├── test_recent_writer.c # Recent games writing - 5 tests +│ ├── test_directory_utils.c # Directory ops (→ minui_file_utils) - 7 tests +│ └── test_binary_file_utils.c # Binary file I/O - 12 tests +├── integration/ # Integration tests (end-to-end tests) +├── fixtures/ # Test data, sample ROMs, configs +├── support/ # Test infrastructure +│ ├── unity/ # Unity test framework +│ ├── fff/ # Fake Function Framework (SDL mocking) +│ │ └── fff.h # Header-only mocking library +│ ├── platform.h # Platform stubs for testing +│ ├── sdl_stubs.h # Minimal SDL type definitions +│ ├── sdl_fakes.h/c # SDL function mocks (fff-based) +│ ├── platform_mocks.h/c # Platform function mocks +│ └── fs_mocks.h/c # File system mocks (--wrap-based) +├── Dockerfile # Test environment (Debian Buster ARM64) +└── README.md # This file ``` ## Organization Philosophy @@ -97,10 +118,10 @@ Tests run in a Debian Buster ARM64 container that matches the platform toolchain make test # Enter Docker container for debugging -make -f Makefile.qa docker-shell +make -f makefile.qa docker-shell # Rebuild Docker image -make -f Makefile.qa docker-build +make -f makefile.qa docker-build ``` ### Native Testing (Advanced) @@ -109,7 +130,7 @@ Run tests directly on your host system (not recommended on macOS due to architec ```bash # Run all tests natively -make -f Makefile.qa test-native +make -f makefile.qa test-native # Run individual test executables ./tests/utils_test # Timing tests (2 tests) @@ -133,7 +154,7 @@ make -f Makefile.qa test-native ### Clean and Rebuild ```bash -make -f Makefile.qa clean-tests +make -f makefile.qa clean-tests make test ``` @@ -176,7 +197,7 @@ int main(void) { } ``` -### 3. Update Makefile.qa +### 3. Update makefile.qa Add your test to the build: @@ -198,6 +219,324 @@ TEST_ASSERT_NULL(pointer) See `support/unity/unity.h` for full list. +## SDL Function Mocking + +MinUI uses SDL extensively for graphics, input, and audio. Testing SDL-dependent code requires **mocking** SDL functions. We use the **Fake Function Framework (fff)** for this. + +### Infrastructure Overview + +``` +tests/support/ +├── fff/ +│ └── fff.h # Fake Function Framework (MIT licensed) +├── sdl_stubs.h # Minimal SDL type definitions +├── sdl_fakes.h # fff-based SDL function fakes (declarations) +├── sdl_fakes.c # fff-based SDL function fakes (definitions) +├── platform_mocks.h # Mock PLAT_* interface +└── platform_mocks.c # Mock PLAT_* implementations +``` + +### What is fff? + +The **Fake Function Framework** is a header-only C mocking library that generates mockable versions of functions using macros. It provides: + +- **Call tracking** - Counts how many times a function was called +- **Argument history** - Records arguments from the last 50 calls +- **Return value control** - Set return values or sequences +- **Custom implementations** - Provide custom fake behavior + +### Using fff for SDL Mocking + +#### Example: Mocking SDL_PollEvent + +```c +#include "../../../support/unity/unity.h" +#include "../../../support/fff/fff.h" +#include "../../../support/sdl_stubs.h" +#include "../../../support/sdl_fakes.h" + +DEFINE_FFF_GLOBALS; + +void setUp(void) { + // Reset fakes before each test + RESET_FAKE(SDL_PollEvent); + FFF_RESET_HISTORY(); +} + +void test_event_handling(void) { + // Configure mock to return "has event" + SDL_PollEvent_fake.return_val = 1; + + SDL_Event event; + int result = SDL_PollEvent(&event); + + // Verify behavior + TEST_ASSERT_EQUAL_INT(1, result); + TEST_ASSERT_EQUAL_INT(1, SDL_PollEvent_fake.call_count); +} +``` + +#### Example: Custom Fake Implementation + +```c +// Custom implementation that calculates text width +int mock_TTF_SizeUTF8(TTF_Font* font, const char* text, int* w, int* h) { + if (w) *w = strlen(text) * 10; // 10 pixels per character + if (h) *h = font->point_size; + return 0; +} + +void test_text_width_calculation(void) { + // Use custom fake + TTF_SizeUTF8_fake.custom_fake = mock_TTF_SizeUTF8; + + TTF_Font font = {.point_size = 16}; + int width, height; + + TTF_SizeUTF8(&font, "Hello", &width, &height); + + TEST_ASSERT_EQUAL_INT(50, width); // 5 chars * 10px + TEST_ASSERT_EQUAL_INT(16, height); // Font size +} +``` + +#### Example: Return Value Sequences + +```c +void test_multiple_events(void) { + // Configure sequence: event, event, no more events + static int return_sequence[] = {1, 1, 0}; + SET_RETURN_SEQ(SDL_PollEvent, return_sequence, 3); + + SDL_Event event; + TEST_ASSERT_EQUAL_INT(1, SDL_PollEvent(&event)); // First event + TEST_ASSERT_EQUAL_INT(1, SDL_PollEvent(&event)); // Second event + TEST_ASSERT_EQUAL_INT(0, SDL_PollEvent(&event)); // No more +} +``` + +### Available SDL Fakes + +**Currently defined:** +- `SDL_PollEvent` - Event polling +- `TTF_SizeUTF8` - Text size calculation + +**To add more fakes:** + +1. Add declaration in `tests/support/sdl_fakes.h`: +```c +DECLARE_FAKE_VALUE_FUNC(int, SDL_BlitSurface, + SDL_Surface*, SDL_Rect*, SDL_Surface*, SDL_Rect*); +``` + +2. Add definition in `tests/support/sdl_fakes.c`: +```c +DEFINE_FAKE_VALUE_FUNC(int, SDL_BlitSurface, + SDL_Surface*, SDL_Rect*, SDL_Surface*, SDL_Rect*); +``` + +3. Reset in your test's `setUp()`: +```c +RESET_FAKE(SDL_BlitSurface); +``` + +### Platform Mocks + +For mocking platform-specific `PLAT_*` functions, use `platform_mocks.c`: + +```c +#include "../../../support/platform_mocks.h" + +void test_battery_status(void) { + // Configure mock battery state + mock_set_battery(75, 1); // 75% charge, charging + + // Call code that uses PLAT_getBatteryStatus() + update_battery_display(); + + // Verify behavior + // ... +} +``` + +### Complete Example + +See `tests/unit/all/common/test_gfx_text.c` for a comprehensive demonstration of fff usage with custom fake implementations. + +### When to Extract vs. Mock + +**Extract to separate module** (like `pad.c`, `collections.c`) when: +- Logic is pure (no SDL dependencies) +- Functions can be reused across components +- Code is tightly coupled to complex global state + +**Use SDL mocking** when: +- Testing functions that directly call SDL +- Verifying interaction with SDL APIs +- Testing error handling (simulating SDL failures) + +**Skip testing** when: +- Function is trivial (simple getter/setter) +- Logic is entirely SDL rendering (test visually instead) +- Would require massive extraction effort for minimal value + +### fff Documentation + +For more fff features, see: +- [fff GitHub](https://github.com/meekrosoft/fff) +- `tests/support/fff/fff.h` (inline documentation) +- `tests/unit/all/common/test_gfx_text.c` (real-world usage examples) + +## File System Function Mocking + +Testing file I/O code requires mocking file system operations. We use **GCC's --wrap linker flag** for this. + +### Infrastructure Overview + +``` +tests/support/ +├── fs_mocks.h # File mocking API +└── fs_mocks.c # File mocking implementation +``` + +### What is --wrap? + +GCC's linker supports `--wrap=symbol` which intercepts function calls: + +```bash +gcc ... -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fgets +``` + +**How it works:** +- Calls to `fopen()` are redirected to `__wrap_fopen()` (your mock) +- Your mock can call `__real_fopen()` for the real implementation +- No code changes needed - pure link-time substitution + +**Platform support:** +- ✅ Linux (GCC/GNU ld) +- ✅ Docker (Debian Buster with GCC) - **This is how we test** +- ❌ macOS (uses ld64, doesn't support --wrap) + +### Using File Mocking + +#### Example: Testing M3U Parser + +```c +#include "../../../support/unity/unity.h" +#include "../../../support/fs_mocks.h" +#include "../../../../workspace/all/common/m3u_parser.h" + +void setUp(void) { + // Reset mock file system before each test + mock_fs_reset(); +} + +void test_parse_m3u_file(void) { + // Create mock files + mock_fs_add_file("/Roms/PS1/FF7.m3u", + "FF7 (Disc 1).bin\nFF7 (Disc 2).bin\n"); + mock_fs_add_file("/Roms/PS1/FF7 (Disc 1).bin", "disc data"); + + // Call production code - it uses real fopen/fgets + char disc_path[256]; + int found = M3U_getFirstDisc("/Roms/PS1/FF7.m3u", disc_path); + + // But it's actually reading from our mock! + TEST_ASSERT_TRUE(found); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF7 (Disc 1).bin", disc_path); +} +``` + +#### Available Mock Functions + +**Currently wrapped:** +- `exists(char* path)` - MinUI's file existence check +- `fopen(const char* path, const char* mode)` - Open files (read mode only) +- `fclose(FILE* stream)` - Close files +- `fgets(char* s, int size, FILE* stream)` - Read lines + +**Mock API:** +```c +void mock_fs_reset(void); // Clear all mock files +void mock_fs_add_file(const char* path, const char* content); // Add mock file +int mock_fs_exists(const char* path); // Check if mock file exists +``` + +### Complete Examples + +**Real-world usage:** +- `tests/unit/all/common/test_m3u_parser.c` - M3U playlist parsing (read-only with mocking) +- `tests/unit/all/common/test_minui_file_utils.c` - File existence checking +- `tests/unit/all/common/test_recent_writer.c` - File writing with real temp files + +### Compilation Requirements + +Tests using file mocking must be compiled with --wrap flags: + +```makefile +tests/my_test: test_my_test.c my_code.c utils.c fs_mocks.c $(TEST_UNITY) + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) \ + -Wl,--wrap=exists \ + -Wl,--wrap=fopen \ + -Wl,--wrap=fclose \ + -Wl,--wrap=fgets +``` + +### When to Use File Mocking + +**Use file mocking when:** +- Testing file parsing logic (map.txt, .m3u, .cue files) +- Testing path construction with existence checks +- Testing functions that read configuration files +- Verifying error handling (file not found, empty files) + +**Don't use file mocking when:** +- Function only manipulates paths (use pure string functions instead) +- Testing actual file I/O performance +- Integration testing (use real temp files) + +### Limitations + +**Current implementation:** +- **Read mode only** - Supports exists(), fopen("r"), fgets(), fclose() +- Files stored as strings in memory (max 8KB per file, 100 files total) +- **Docker-only** - Requires GCC --wrap (won't compile on macOS with clang/ld64) +- Comprehensive test coverage for all text file parsers (map.txt, M3U, collections, recent.txt) + +**Limitations and alternatives:** +- **Write operations** (fputs, fprintf, fwrite) - Use real temp files instead +- **Directory operations** (opendir, readdir) - Use real temp directories instead +- **Binary I/O** (fread, fwrite with binary data) - Use real temp files instead + +Using real temp files for these operations is more reliable, works cross-platform, +and avoids the complexity of mocking glibc's internal FILE structure validation. + +### Example: Testing with Temp Files + +For write operations or directory testing, use mktemp and cleanup: + +```c +void test_saveRecents(void) { + // Create temp file + char temp_path[] = "/tmp/recent_test_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + // Test the write operation + saveRecents(temp_path); + + // Read back and verify + FILE* f = fopen(temp_path, "r"); + TEST_ASSERT_NOT_NULL(f); + // ... verify content ... + fclose(f); + + // Cleanup + unlink(temp_path); +} +``` + ## Test Guidelines ### Good Test Characteristics @@ -241,6 +580,52 @@ void test_getEmuName_with_parens(void) { } ``` +## Test Summary + +**Total: 364 tests across 16 test suites** +- **342 unit tests** - Testing individual modules in isolation +- **22 integration tests** - Testing modules working together with real file I/O + +### Extracted Modules (Testable Logic) + +These modules were extracted from large files (api.c, minui.c, minarch.c) to enable comprehensive unit testing: + +| Module | Lines | Tests | Extracted From | Key Functions | +|--------|-------|-------|----------------|---------------| +| utils.c | 703 | 100 | (original) | String, file, name, date, math utilities | +| pad.c | 183 | 21 | api.c | Button state machine, analog input | +| collections.c | 193 | 30 | minui.c | Array, Hash data structures | +| gfx_text.c | 170 | 32 | api.c | Text truncation, wrapping, sizing | +| audio_resampler.c | 90 | 18 | api.c | Bresenham sample rate conversion | +| minarch_paths.c | 77 | 16 | minarch.c | Save file path generation | +| minui_utils.c | 48 | 17 | minui.c | Index char, console dir detection | +| m3u_parser.c | 132 | 20 | minui.c | M3U playlist parsing (getFirstDisc + getAllDiscs) | +| minui_file_utils.c | 130 | 25 | minui.c | File/dir checking (hasEmu, hasCue, hasM3u, hasNonHiddenFiles) | +| map_parser.c | 64 | 22 | minui.c/minarch.c | ROM display name aliasing (map.txt) | +| collection_parser.c | 70 | 11 | minui.c | Custom ROM list parsing (.txt files) | +| recent_file.c | 95 | 18 | minui.c | Recent games read/write (parse + save) | +| binary_file_utils.c | 42 | 12 | minarch.c | Binary file read/write (fread/fwrite) | +| **Total** | **1,997** | **342** | | | + +### Testing Technologies + +**Mocking Frameworks:** +- **fff (Fake Function Framework)** - SDL function mocking (header-only) +- **GCC --wrap** - File system function mocking for reads (link-time substitution) + +**Testing Approaches:** +- **File mocking (--wrap)**: Read-only text file operations (exists, fopen("r"), fgets) +- **Real temp files**: Write operations (mkstemp + fopen("w"), fputs, fwrite) +- **Real temp directories**: Directory operations (mkdtemp + opendir, readdir) + +**What We Can Test:** +- SDL functions (SDL_PollEvent, TTF_SizeUTF8, etc.) - fff mocks +- Platform functions (PLAT_getBatteryStatus, PLAT_pollInput, etc.) - fff mocks +- Text file parsing (map.txt, M3U, collections, recent.txt) - file mocking +- File writing (Recent_save) - real temp files +- Binary file I/O (fread/fwrite) - real temp files +- Directory checking (hasNonHiddenFiles) - real temp directories + ## Current Test Coverage ### workspace/all/common/utils/utils.c - ✅ 2 tests @@ -304,11 +689,309 @@ void test_getEmuName_with_parens(void) { **Note:** Extracted from `api.c` for reusability and testability. +### workspace/all/common/pad.c - ✅ 21 tests +**File:** `tests/unit/all/common/test_api_pad.c` + +- Button state management (PAD_reset, PAD_setAnalog) +- Analog stick deadzone handling +- Opposite direction cancellation (left/right, up/down) +- Button repeat timing +- Query functions (anyJustPressed, justPressed, isPressed, justReleased, justRepeated) +- Menu tap detection (PAD_tappedMenu) + +**Coverage:** Complete coverage of input state machine logic. + +**Note:** Extracted from `api.c` for testability without SDL dependencies. + +### workspace/all/common/collections.c - ✅ 30 tests +**File:** `tests/unit/all/common/test_collections.c` + +- Array lifecycle (Array_new, Array_free) +- Array operations (push, pop, unshift, reverse) +- Capacity growth (doubling when full) +- StringArray operations (indexOf, StringArray_free) +- Hash map operations (Hash_new, Hash_set, Hash_get, Hash_free) +- Integration tests (ROM alias maps, recent games lists) + +**Coverage:** Complete coverage of dynamic array and hash map data structures. + +**Note:** Extracted from `minui.c` for reusability across all components. + +### workspace/all/common/gfx_text.c - ✅ 32 tests +**File:** `tests/unit/all/common/test_gfx_text.c` + +- GFX_truncateText() - Text truncation with ellipsis (8 tests) +- GFX_wrapText() - Multi-line text wrapping (16 tests) +- GFX_sizeText() - Multi-line text bounding box calculation (8 tests) + +**Coverage:** Complete coverage of text manipulation algorithms. + +**Note:** Extracted from `api.c`, uses fff to mock TTF_SizeUTF8. + +### workspace/all/common/audio_resampler.c - ✅ 18 tests +**File:** `tests/unit/all/common/test_audio_resampler.c` + +- Nearest-neighbor sample rate conversion +- Bresenham-like algorithm for frame duplication/skipping +- Upsampling (44100 -> 48000 Hz) +- Downsampling (48000 -> 44100 Hz) +- Realistic scenarios (1 second of audio) +- Ring buffer integration + +**Coverage:** Complete coverage of audio resampling algorithm. + +**Note:** Extracted from `api.c`'s SND_resampleNear(), pure algorithm with no SDL dependencies. + +### workspace/all/common/minarch_paths.c - ✅ 16 tests +**File:** `tests/unit/all/common/test_minarch_paths.c` + +- SRAM save file path generation (.sav files) +- RTC file path generation (.rtc files) +- Save state path generation (.st0-.st9 files) +- Config file path generation (.cfg files with device tags) + +**Coverage:** Complete coverage of MinArch save file path logic. + +**Note:** Extracted from `minarch.c`, pure sprintf logic. + +### workspace/all/common/minui_utils.c - ✅ 17 tests +**File:** `tests/unit/all/common/test_minui_utils.c` + +- MinUI_getIndexChar() - Alphabetical indexing for ROM navigation (7 tests) +- MinUI_isConsoleDir() - Console directory classification (8 tests) +- Integration tests (2 tests) + +**Coverage:** Complete coverage of MinUI helper utilities. + +**Note:** Extracted from `minui.c`, pure string logic. + +### workspace/all/common/m3u_parser.c - ✅ 20 tests +**File:** `tests/unit/all/common/test_m3u_parser.c` + +- M3U_getFirstDisc() - First disc extraction (8 tests) +- M3U_getAllDiscs() - Full disc list parsing (12 tests) +- Empty line handling +- Windows newline support +- Path construction (relative to M3U location) +- Error handling (missing files, empty playlists) +- Disc naming and numbering + +**Coverage:** Complete coverage of M3U parsing logic (getFirstDisc + getAllDiscs). + +**Note:** Extracted from `minui.c`, uses file system mocking. + +### workspace/all/common/minui_file_utils.c - ✅ 25 tests +**Files:** +- `tests/unit/all/common/test_minui_file_utils.c` (18 tests) +- `tests/unit/all/common/test_directory_utils.c` (7 tests) + +**File existence checking (18 tests):** +- MinUI_hasEmu() - Emulator availability checking (5 tests) +- MinUI_hasCue() - CUE file detection for disc games (4 tests) +- MinUI_hasM3u() - M3U playlist detection (5 tests) +- Integration tests (multi-disc workflow) (4 tests) + +**Directory content checking (7 tests):** +- MinUI_hasNonHiddenFiles() - Directory content checking +- Empty directory detection +- Hidden file filtering (.dotfiles, .DS_Store, etc.) +- Mixed content (hidden + visible files) +- Subdirectory handling +- Nonexistent directory error handling + +**Coverage:** Complete coverage of file and directory checking utilities. + +**Note:** Extracted from `minui.c` hasEmu/hasCue/hasM3u/hasCollections/hasRoms. File tests use mocking, directory tests use real temp directories with mkdtemp(). + +### workspace/all/common/map_parser.c - ✅ 22 tests +**File:** `tests/unit/all/common/test_map_parser.c` + +- Map_getAlias() - ROM display name aliasing +- Tab-delimited format parsing (filenamedisplay name) +- Hidden ROM detection (alias starts with '.') +- Basic parsing (with and without aliases) +- Integration tests (real-world map.txt files) +- Error handling (file not found, empty files, missing ROMs) + +**Coverage:** Complete coverage of map.txt parsing logic. + +**Note:** Extracted from `minui.c`'s Directory_index() and `minarch.c`'s getAlias(), uses file system mocking. + +### workspace/all/common/collection_parser.c - ✅ 11 tests +**File:** `tests/unit/all/common/test_collection_parser.c` + +- Collection_parse() - Custom ROM list parsing +- Plain text format (one ROM path per line) +- ROM validation (only includes existing files) +- PAK detection (identifies .pak files) +- Empty line handling +- Integration tests (multi-platform collections) +- Error handling (file not found, all ROMs missing) + +**Coverage:** Complete coverage of collection .txt parsing logic. + +**Note:** Extracted from `minui.c`'s getCollection(), uses file system mocking. + +### workspace/all/common/recent_file.c - ✅ 18 tests (13 read + 5 write) +**Files:** +- `tests/unit/all/common/test_recent_file.c` (13 tests) - Recent_parse() +- `tests/unit/all/common/test_recent_writer.c` (5 tests) - Recent_save() + +**Read operations (uses file mocking):** +- Recent_parse() - Tab-delimited format parsing +- ROM validation (only includes existing files) +- Order preservation (newest first) +- Format handling (Windows newlines, special characters) +- Error handling (file not found, empty files) + +**Write operations (uses real temp files):** +- Recent_save() - Writes entries to recent.txt +- Single/multiple entries with/without aliases +- Empty array handling +- File creation error handling + +**Coverage:** Complete coverage of recent.txt read/write operations. + +**Note:** Extracted from `minui.c` loadRecents()/saveRecents(). Uses hybrid approach: file mocking for reads, real temp files for writes. + +### workspace/all/common/binary_file_utils.c - ✅ 12 tests +**File:** `tests/unit/all/common/test_binary_file_utils.c` + +- BinaryFile_read() - Binary file reading with fread() +- BinaryFile_write() - Binary file writing with fwrite() +- Small buffers (5 bytes) +- Large buffers (1KB) +- SRAM-like data (32KB, like Game Boy saves) +- RTC-like data (8 bytes, like Game Boy RTC) +- Error handling (null buffers, zero size, invalid paths) +- Partial reads +- File overwriting + +**Coverage:** Complete coverage of binary file I/O patterns used in minarch.c. + +**Note:** Extracted from `minarch.c` SRAM_read()/SRAM_write() patterns. Uses real temp files with mkstemp(). + +## Integration Tests + +**Location:** `tests/integration/` + +Integration tests verify that multiple extracted modules work together correctly with real file I/O. Unlike unit tests that test individual functions in isolation, integration tests exercise realistic workflows that span multiple components. + +### Approach: Component Integration + +Integration tests use a **component integration** approach: +- Test extracted modules working together (M3U parser + Map parser + Recent file, etc.) +- Use real temp directories and files (created with mkdtemp/mkstemp) +- No mocking - tests real file I/O and data flow between components +- Easy to maintain - follows same pattern as existing 342 unit tests + +**Why not test the full minui.c/minarch.c applications?** +The main launcher and frontend code is tightly coupled to SDL rendering and event loops. Testing these would require complex subprocess management or invasive code changes. Instead, we test the extracted business logic modules working together - this provides excellent coverage with minimal complexity. + +### Available Integration Tests + +**File:** `tests/integration/test_workflows.c` - **22 comprehensive tests** + +**Multi-Disc Workflows (5 tests):** +1. `test_multi_disc_game_complete_workflow` - M3U + Map + Recent end-to-end +2. `test_multi_disc_detection` - hasM3u + hasCue together +3. `test_m3u_first_vs_all_consistency` - getFirstDisc/getAllDiscs match +4. `test_m3u_with_multiple_cues` - M3U + individual CUE files +5. `test_nested_directories` - Deep directory structures + +**Collection Workflows (4 tests):** +6. `test_collection_with_aliases` - Multi-system with map.txt +7. `test_collection_with_m3u_games` - Collection of multi-disc games +8. `test_multi_system_collection_workflow` - GB + NES + PS1 mixed features +9. `test_empty_directory_collection` - Empty directory handling + +**Recent Games Workflows (2 tests):** +10. `test_recent_games_roundtrip` - Save/load/modify persistence +11. `test_recent_with_save_states` - Recent + save state integration + +**MinArch Save File Workflows (4 tests):** +12. `test_minarch_save_state_workflow` - Save state path + binary I/O +13. `test_minarch_sram_rtc_workflow` - SRAM + RTC file handling +14. `test_all_save_slots` - All 10 slots (0-9) +15. `test_auto_resume_slot_9` - Auto-resume workflow + +**Config File Workflows (2 tests):** +16. `test_minarch_config_file_integration` - Game vs global configs +17. `test_config_device_tags` - Device-specific config isolation + +**Advanced Integration (5 tests):** +18. `test_file_detection_integration` - All file detection utilities together +19. `test_hidden_roms_in_map` - Hidden ROM handling (alias starts with '.') +20. `test_rom_with_all_features` - M3U + CUE + alias + save + recent +21. `test_multi_platform_save_isolation` - Cross-platform userdata +22. `test_error_handling_integration` - Missing files, empty collections + +### Running Integration Tests + +Integration tests run automatically with `make test` (Docker-based): + +```bash +make test # Run all tests (unit + integration) +make -f makefile.qa test-native # Run natively (advanced) +``` + +Integration tests are included in the standard test suite and run in Docker like all other tests. + +### Adding New Integration Tests + +Follow the same pattern as existing integration tests: + +```c +// 1. Include required modules +#include "../../workspace/all/common/m3u_parser.h" +#include "../../workspace/all/common/map_parser.h" +#include "../support/unity/unity.h" +#include "integration_support.h" + +// 2. Use setUp/tearDown for temp directory management +static char test_dir[256]; + +void setUp(void) { + strcpy(test_dir, "/tmp/minui_integration_XXXXXX"); + create_test_minui_structure(test_dir); +} + +void tearDown(void) { + rmdir_recursive(test_dir); +} + +// 3. Write integration test +void test_my_workflow(void) { + // Setup: Create real files + char path[512]; + snprintf(path, sizeof(path), "%s/Roms/GB/game.gb", test_dir); + create_test_rom(path); + + // Test: Call multiple modules + // Assert: Verify integration works + + // Cleanup: Handled by tearDown() +} +``` + +### Integration Test Support Utilities + +**File:** `tests/integration/integration_support.h/c` + +Helper functions for creating test data structures: + +- `create_test_minui_structure()` - Creates temp MinUI directory structure +- `create_test_rom()` - Creates placeholder ROM file +- `create_test_m3u()` - Creates M3U file with disc entries +- `create_test_map()` - Creates map.txt with ROM aliases +- `create_test_collection()` - Creates collection .txt file +- `rmdir_recursive()` - Cleans up temp directories + +These utilities make it easy to set up realistic test scenarios. + ### Todo -- [ ] workspace/all/common/api.c (remaining functions require SDL mocks) -- [ ] workspace/all/minui/minui.c (integration tests) -- [ ] workspace/all/minarch/minarch.c (integration tests) -- [ ] Integration tests for full launch workflow +- [ ] Additional api.c GFX rendering functions (mostly SDL pixel operations) +- [x] Integration tests for MinUI/MinArch workflows (22 tests implemented, all passing) ## Continuous Integration @@ -333,10 +1016,10 @@ CI systems should have Docker available. The test environment will automatically ### Debug in Docker Container ```bash # Enter the test container -make -f Makefile.qa docker-shell +make -f makefile.qa docker-shell # Inside container, build and run tests -make -f Makefile.qa clean-tests test-native +make -f makefile.qa clean-tests test-native # Build with debug symbols gcc -g -o tests/utils_test_debug tests/unit/all/common/test_utils.c \ diff --git a/tests/fs_mocking_example b/tests/fs_mocking_example new file mode 100755 index 00000000..d93d8c3b Binary files /dev/null and b/tests/fs_mocking_example differ diff --git a/tests/integration/integration_support.c b/tests/integration/integration_support.c new file mode 100644 index 00000000..baa4fa5d --- /dev/null +++ b/tests/integration/integration_support.c @@ -0,0 +1,229 @@ +/** + * integration_support.c - Support utilities for integration tests implementation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "integration_support.h" + +/** + * Creates nested directories recursively. + */ +static int mkdir_recursive(const char* path) { + char tmp[512]; + char* p = NULL; + size_t len; + + snprintf(tmp, sizeof(tmp), "%s", path); + len = strlen(tmp); + if (tmp[len - 1] == '/') + tmp[len - 1] = 0; + + for (p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = 0; + if (mkdir(tmp, 0755) != 0 && errno != EEXIST) + return 0; + *p = '/'; + } + } + if (mkdir(tmp, 0755) != 0 && errno != EEXIST) + return 0; + + return 1; +} + +char* create_test_minui_structure(char* template) { + // Create temp directory + if (!mkdtemp(template)) + return NULL; + + // Create subdirectories + char path[512]; + + // Roms directory + snprintf(path, sizeof(path), "%s/Roms", template); + if (!mkdir_recursive(path)) + return NULL; + + // .userdata/.minui directory + snprintf(path, sizeof(path), "%s/.userdata/.minui", template); + if (!mkdir_recursive(path)) + return NULL; + + // Collections directory + snprintf(path, sizeof(path), "%s/Collections", template); + if (!mkdir_recursive(path)) + return NULL; + + // Emus directory (for emulator paks) + snprintf(path, sizeof(path), "%s/Emus", template); + if (!mkdir_recursive(path)) + return NULL; + + // Paks directory (for emulator detection) + snprintf(path, sizeof(path), "%s/Paks/Emus", template); + if (!mkdir_recursive(path)) + return NULL; + + return template; +} + +int create_test_rom(const char* path) { + // Create parent directory if needed + char dir[512]; + snprintf(dir, sizeof(dir), "%s", path); + char* last_slash = strrchr(dir, '/'); + if (last_slash) { + *last_slash = '\0'; + if (!mkdir_recursive(dir)) + return 0; + } + + // Create empty file + FILE* f = fopen(path, "w"); + if (!f) + return 0; + + // Write minimal data so file exists and has size + fwrite("TEST", 1, 4, f); + fclose(f); + return 1; +} + +int create_test_m3u(const char* path, const char** disc_names, int disc_count) { + // Create parent directory + char dir[512]; + snprintf(dir, sizeof(dir), "%s", path); + char* last_slash = strrchr(dir, '/'); + if (last_slash) { + *last_slash = '\0'; + if (!mkdir_recursive(dir)) + return 0; + } + + // Write M3U file + FILE* f = fopen(path, "w"); + if (!f) + return 0; + + for (int i = 0; i < disc_count; i++) { + fprintf(f, "%s\n", disc_names[i]); + } + + fclose(f); + return 1; +} + +int create_test_map(const char* path, const char** rom_names, const char** aliases, + int count) { + // Create parent directory + char dir[512]; + snprintf(dir, sizeof(dir), "%s", path); + char* last_slash = strrchr(dir, '/'); + if (last_slash) { + *last_slash = '\0'; + if (!mkdir_recursive(dir)) + return 0; + } + + // Write map.txt file (tab-delimited) + FILE* f = fopen(path, "w"); + if (!f) + return 0; + + for (int i = 0; i < count; i++) { + fprintf(f, "%s\t%s\n", rom_names[i], aliases[i]); + } + + fclose(f); + return 1; +} + +int create_test_collection(const char* path, const char** rom_paths, int count) { + // Create parent directory + char dir[512]; + snprintf(dir, sizeof(dir), "%s", path); + char* last_slash = strrchr(dir, '/'); + if (last_slash) { + *last_slash = '\0'; + if (!mkdir_recursive(dir)) + return 0; + } + + // Write collection file (one ROM path per line) + FILE* f = fopen(path, "w"); + if (!f) + return 0; + + for (int i = 0; i < count; i++) { + fprintf(f, "%s\n", rom_paths[i]); + } + + fclose(f); + return 1; +} + +int create_parent_dir(const char* file_path) { + char dir[512]; + snprintf(dir, sizeof(dir), "%s", file_path); + + // Find last slash + char* last_slash = strrchr(dir, '/'); + if (!last_slash) + return 0; + + // Truncate to get directory + *last_slash = '\0'; + + // Create directory recursively + return mkdir_recursive(dir); +} + +int rmdir_recursive(const char* path) { + DIR* d = opendir(path); + if (!d) + return 0; + + struct dirent* entry; + char file_path[512]; + int success = 1; + + while ((entry = readdir(d)) != NULL) { + // Skip . and .. + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name); + + struct stat st; + if (stat(file_path, &st) != 0) { + success = 0; + continue; + } + + if (S_ISDIR(st.st_mode)) { + // Recursively remove subdirectory + if (!rmdir_recursive(file_path)) + success = 0; + } else { + // Remove file + if (unlink(file_path) != 0) + success = 0; + } + } + + closedir(d); + + // Remove the directory itself + if (rmdir(path) != 0) + success = 0; + + return success; +} diff --git a/tests/integration/integration_support.h b/tests/integration/integration_support.h new file mode 100644 index 00000000..bd52cc80 --- /dev/null +++ b/tests/integration/integration_support.h @@ -0,0 +1,87 @@ +/** + * integration_support.h - Support utilities for integration tests + * + * Provides helper functions for creating realistic test directory structures + * and test data files for MinUI integration testing. + */ + +#ifndef __INTEGRATION_SUPPORT_H__ +#define __INTEGRATION_SUPPORT_H__ + +/** + * Creates a temporary MinUI directory structure for testing. + * + * Creates a directory tree with: + * - /Roms/ (ROM directories) + * - /.userdata/.minui/ (recent games, state) + * - /Collections/ (custom ROM lists) + * + * @param template Template for mkdtemp (e.g., "/tmp/minui_test_XXXXXX") + * @return Pointer to template (now contains actual path), or NULL on failure + * + * @note Caller must remove directory with rmdir_recursive() when done + */ +char* create_test_minui_structure(char* template); + +/** + * Creates a test ROM file (empty placeholder). + * + * @param path Full path to ROM file + * @return 1 on success, 0 on failure + */ +int create_test_rom(const char* path); + +/** + * Creates a test M3U file with disc entries. + * + * @param path Full path to .m3u file + * @param disc_names Array of disc filenames (relative to M3U directory) + * @param disc_count Number of discs + * @return 1 on success, 0 on failure + */ +int create_test_m3u(const char* path, const char** disc_names, int disc_count); + +/** + * Creates a test map.txt file with ROM aliases. + * + * @param path Full path to map.txt + * @param rom_names Array of ROM filenames + * @param aliases Array of display names + * @param count Number of entries + * @return 1 on success, 0 on failure + */ +int create_test_map(const char* path, const char** rom_names, + const char** aliases, int count); + +/** + * Creates a test collection .txt file. + * + * @param path Full path to collection file + * @param rom_paths Array of full ROM paths + * @param count Number of ROMs + * @return 1 on success, 0 on failure + */ +int create_test_collection(const char* path, const char** rom_paths, int count); + +/** + * Creates parent directories for a file path. + * + * Extracts the directory from a file path and creates it recursively. + * Example: "/tmp/foo/bar/file.txt" creates "/tmp/foo/bar/" + * + * @param file_path Full path to file + * @return 1 on success, 0 on failure + */ +int create_parent_dir(const char* file_path); + +/** + * Recursively removes a directory and all its contents. + * + * @param path Directory to remove + * @return 1 on success, 0 on failure + * + * @warning Destructive operation - use only with test directories! + */ +int rmdir_recursive(const char* path); + +#endif // __INTEGRATION_SUPPORT_H__ diff --git a/tests/integration/test_workflows.c b/tests/integration/test_workflows.c new file mode 100644 index 00000000..75049031 --- /dev/null +++ b/tests/integration/test_workflows.c @@ -0,0 +1,1499 @@ +/** + * test_workflows.c - Integration tests for MinUI workflows + * + * Tests multiple components working together with real file I/O to verify + * end-to-end functionality. Uses real temp directories and files instead of mocks. + * + * Test Scenarios: + * 1. Multi-disc game workflow (M3U + Map + Recent integration) + * 2. Collection with aliases (Collection + Map integration) + * 3. Recent games round-trip (Recent parse + save) + */ + +#include +#include +#include + +#include "../../workspace/all/common/binary_file_utils.h" +#include "../../workspace/all/common/collection_parser.h" +#include "../../workspace/all/common/m3u_parser.h" +#include "../../workspace/all/common/map_parser.h" +#include "../../workspace/all/common/minarch_paths.h" +#include "../../workspace/all/common/minui_file_utils.h" +#include "../../workspace/all/common/recent_file.h" +#include "../support/platform.h" +#include "../support/unity/unity.h" +#include "integration_support.h" + +// Test directory (created in setUp, removed in tearDown) +static char test_dir[256]; + +void setUp(void) { + // Create temp directory for each test + strcpy(test_dir, "/tmp/minui_integration_XXXXXX"); + char* result = create_test_minui_structure(test_dir); + TEST_ASSERT_NOT_NULL(result); +} + +void tearDown(void) { + // Clean up temp directory + int removed = rmdir_recursive(test_dir); + TEST_ASSERT_TRUE(removed); +} + +/////////////////////////////// +// Multi-Disc Game Workflow Tests +/////////////////////////////// + +/** + * Integration test: Multi-disc game with M3U, map.txt, and recent.txt + * + * Workflow: + * 1. Create multi-disc PS1 game (Final Fantasy VII) + * 2. Parse M3U to get all discs + * 3. Get display aliases from map.txt + * 4. Save to recent games + * 5. Load recent games and verify + */ +void test_multi_disc_game_complete_workflow(void) { + char path[512]; + + // Step 1: Create realistic multi-disc game structure + // Create disc files + const char* disc_files[] = {"FF7 (Disc 1).bin", "FF7 (Disc 2).bin", + "FF7 (Disc 3).bin"}; + + for (int i = 0; i < 3; i++) { + snprintf(path, sizeof(path), "%s/Roms/PS1/%s", test_dir, disc_files[i]); + TEST_ASSERT_TRUE(create_test_rom(path)); + } + + // Create M3U file + snprintf(path, sizeof(path), "%s/Roms/PS1/FF7.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, disc_files, 3)); + + // Create map.txt with display names + const char* rom_names[] = {"FF7 (Disc 1).bin", "FF7 (Disc 2).bin", + "FF7 (Disc 3).bin"}; + const char* aliases[] = {"Final Fantasy VII - Disc 1", + "Final Fantasy VII - Disc 2", + "Final Fantasy VII - Disc 3"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, rom_names, aliases, 3)); + + // Step 2: Parse M3U and get all discs + char m3u_path[512]; + snprintf(m3u_path, sizeof(m3u_path), "%s/Roms/PS1/FF7.m3u", test_dir); + + int disc_count = 0; + M3U_Disc** discs = M3U_getAllDiscs(m3u_path, &disc_count); + TEST_ASSERT_NOT_NULL(discs); + TEST_ASSERT_EQUAL(3, disc_count); + + // Verify disc paths + snprintf(path, sizeof(path), "%s/Roms/PS1/FF7 (Disc 1).bin", test_dir); + TEST_ASSERT_EQUAL_STRING(path, discs[0]->path); + TEST_ASSERT_EQUAL_STRING("Disc 1", discs[0]->name); + TEST_ASSERT_EQUAL(1, discs[0]->disc_number); + + // Step 3: Get display aliases from map.txt + char alias[256]; + Map_getAlias(discs[0]->path, alias); + // Note: Map_getAlias modifies the alias buffer only if found, + // so we need to initialize it first + strcpy(alias, discs[0]->name); // Default to disc name + Map_getAlias(discs[0]->path, alias); + TEST_ASSERT_EQUAL_STRING("Final Fantasy VII - Disc 1", alias); + + // Step 4: Save to recent games + // NOTE: Recent_save expects relative paths (starting with /Roms...) + Recent_Entry* entry = malloc(sizeof(Recent_Entry)); + entry->path = strdup("/Roms/PS1/FF7.m3u"); // Relative path + entry->alias = strdup(alias); + + Recent_Entry** entries = malloc(sizeof(Recent_Entry*)); + entries[0] = entry; + + char recent_path[512]; + snprintf(recent_path, sizeof(recent_path), "%s/.userdata/.minui/recent.txt", + test_dir); + + int saved = Recent_save(recent_path, entries, 1); + TEST_ASSERT_TRUE(saved); + + // Step 5: Load recent games and verify integration + int loaded_count = 0; + Recent_Entry** loaded = Recent_parse(recent_path, test_dir, &loaded_count); + TEST_ASSERT_NOT_NULL(loaded); + TEST_ASSERT_EQUAL(1, loaded_count); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF7.m3u", loaded[0]->path); + TEST_ASSERT_EQUAL_STRING(alias, loaded[0]->alias); + + // Cleanup + M3U_freeDiscs(discs, disc_count); + Recent_freeEntries(entries, 1); + Recent_freeEntries(loaded, loaded_count); +} + +/** + * Integration test: M3U detection and file utilities + * + * Workflow: + * 1. Create game with .m3u and .cue files + * 2. Verify MinUI_hasM3u() detects M3U + * 3. Verify MinUI_hasCue() detects CUE + * 4. Test interaction between M3U and CUE detection + */ +void test_multi_disc_detection(void) { + char path[512]; + + // Create multi-disc game structure + // MinUI_hasM3u expects: /Roms/PS1/Game/disc.bin and looks for /Roms/PS1/Game.m3u + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + const char* disc_files[] = {"Game/disc1.bin"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/Game.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, disc_files, 1)); + + // Create CUE file in game directory + // MinUI_hasCue expects directory path and looks for dir/dirname.cue + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/Game.cue", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Test M3U detection + char rom_path[512]; + snprintf(rom_path, sizeof(rom_path), "%s/Roms/PS1/Game/disc1.bin", test_dir); + + char m3u_path[512]; + int has_m3u = MinUI_hasM3u(rom_path, m3u_path); + TEST_ASSERT_TRUE(has_m3u); + TEST_ASSERT_EQUAL_STRING_LEN("/Roms/PS1/Game.m3u", m3u_path + strlen(test_dir), + strlen("/Roms/PS1/Game.m3u")); + + // Test CUE detection (expects directory path, not ROM path) + char dir_path[512]; + snprintf(dir_path, sizeof(dir_path), "%s/Roms/PS1/Game", test_dir); + + char cue_path[512]; + int has_cue = MinUI_hasCue(dir_path, cue_path); + TEST_ASSERT_TRUE(has_cue); + + // Both should be detected + TEST_ASSERT_TRUE(has_m3u && has_cue); +} + +/////////////////////////////// +// Collection Integration Tests +/////////////////////////////// + +/** + * Integration test: Collection with custom ROM aliases + * + * Workflow: + * 1. Create collection file with ROMs from multiple systems + * 2. Create map.txt files with custom names + * 3. Parse collection + * 4. Verify aliases work across systems + */ +void test_collection_with_aliases(void) { + char path[512]; + + // Create ROMs across multiple systems + snprintf(path, sizeof(path), "%s/Roms/GB/mario.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/NES/zelda.nes", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/SNES/metroid.smc", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Create map.txt files for each system + const char* gb_names[] = {"mario.gb"}; + const char* gb_aliases[] = {"Super Mario Land"}; + snprintf(path, sizeof(path), "%s/Roms/GB/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, gb_names, gb_aliases, 1)); + + const char* nes_names[] = {"zelda.nes"}; + const char* nes_aliases[] = {"The Legend of Zelda"}; + snprintf(path, sizeof(path), "%s/Roms/NES/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, nes_names, nes_aliases, 1)); + + // Create collection file + char mario_path[256], zelda_path[256], metroid_path[256]; + snprintf(mario_path, sizeof(mario_path), "/Roms/GB/mario.gb"); + snprintf(zelda_path, sizeof(zelda_path), "/Roms/NES/zelda.nes"); + snprintf(metroid_path, sizeof(metroid_path), "/Roms/SNES/metroid.smc"); + + const char* collection_paths[] = {mario_path, zelda_path, metroid_path}; + snprintf(path, sizeof(path), "%s/Collections/Favorites.txt", test_dir); + TEST_ASSERT_TRUE(create_test_collection(path, collection_paths, 3)); + + // Parse collection + int entry_count = 0; + Collection_Entry** entries = Collection_parse(path, test_dir, &entry_count); + TEST_ASSERT_NOT_NULL(entries); + TEST_ASSERT_EQUAL(3, entry_count); + + // Verify collection entries exist + char expected_path[512]; + snprintf(expected_path, sizeof(expected_path), "%s/Roms/GB/mario.gb", test_dir); + TEST_ASSERT_EQUAL_STRING(expected_path, entries[0]->path); + + // Now verify map aliases work for collection entries + char alias[256]; + + // Mario + strcpy(alias, ""); + Map_getAlias(entries[0]->path, alias); + TEST_ASSERT_EQUAL_STRING("Super Mario Land", alias); + + // Zelda + strcpy(alias, ""); + Map_getAlias(entries[1]->path, alias); + TEST_ASSERT_EQUAL_STRING("The Legend of Zelda", alias); + + // Metroid (no map.txt, so no alias should be set) + strcpy(alias, "Original Name"); + Map_getAlias(entries[2]->path, alias); + TEST_ASSERT_EQUAL_STRING("Original Name", alias); // Unchanged + + // Cleanup + Collection_freeEntries(entries, entry_count); +} + +/** + * Integration test: Recent games round-trip with multiple entries + * + * Workflow: + * 1. Create several ROMs + * 2. Save multiple entries to recent.txt + * 3. Load and verify order preserved + * 4. Modify list and save again + * 5. Verify changes persist + */ +void test_recent_games_roundtrip(void) { + char path[512]; + + // Create ROMs + snprintf(path, sizeof(path), "%s/Roms/GB/game1.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/GB/game2.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/NES/game3.nes", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Create recent entries + Recent_Entry** entries = malloc(3 * sizeof(Recent_Entry*)); + + entries[0] = malloc(sizeof(Recent_Entry)); + snprintf(path, sizeof(path), "/Roms/GB/game1.gb"); + entries[0]->path = strdup(path); + entries[0]->alias = strdup("Game One"); + + entries[1] = malloc(sizeof(Recent_Entry)); + snprintf(path, sizeof(path), "/Roms/GB/game2.gb"); + entries[1]->path = strdup(path); + entries[1]->alias = NULL; // No alias + + entries[2] = malloc(sizeof(Recent_Entry)); + snprintf(path, sizeof(path), "/Roms/NES/game3.nes"); + entries[2]->path = strdup(path); + entries[2]->alias = strdup("Game Three"); + + // Save to recent.txt + char recent_path[512]; + snprintf(recent_path, sizeof(recent_path), "%s/.userdata/.minui/recent.txt", + test_dir); + + int saved = Recent_save(recent_path, entries, 3); + TEST_ASSERT_TRUE(saved); + + // Load and verify + int loaded_count = 0; + Recent_Entry** loaded = Recent_parse(recent_path, test_dir, &loaded_count); + TEST_ASSERT_NOT_NULL(loaded); + TEST_ASSERT_EQUAL(3, loaded_count); + + // Verify order and content (loaded entries have relative paths) + TEST_ASSERT_EQUAL_STRING("/Roms/GB/game1.gb", loaded[0]->path); + TEST_ASSERT_EQUAL_STRING("Game One", loaded[0]->alias); + + TEST_ASSERT_EQUAL_STRING("/Roms/GB/game2.gb", loaded[1]->path); + TEST_ASSERT_NULL(loaded[1]->alias); + + TEST_ASSERT_EQUAL_STRING("/Roms/NES/game3.nes", loaded[2]->path); + TEST_ASSERT_EQUAL_STRING("Game Three", loaded[2]->alias); + + // Modify list (remove middle entry, add new one at front) + Recent_Entry** modified = malloc(3 * sizeof(Recent_Entry*)); + + // New entry at front (most recent) + modified[0] = malloc(sizeof(Recent_Entry)); + snprintf(path, sizeof(path), "/Roms/NES/game3.nes"); + modified[0]->path = strdup(path); + modified[0]->alias = strdup("Game Three (Updated)"); + + // Keep first and third from original + modified[1] = malloc(sizeof(Recent_Entry)); + snprintf(path, sizeof(path), "/Roms/GB/game1.gb"); + modified[1]->path = strdup(path); + modified[1]->alias = strdup("Game One"); + + modified[2] = malloc(sizeof(Recent_Entry)); + snprintf(path, sizeof(path), "%s/Roms/GB/game2.gb", test_dir); + // Remove the test_dir prefix for storage (paths are relative) + snprintf(path, sizeof(path), "/Roms/GB/game2.gb"); + modified[2]->path = strdup(path); + modified[2]->alias = NULL; + + // Save modified list + saved = Recent_save(recent_path, modified, 3); + TEST_ASSERT_TRUE(saved); + + // Load again and verify changes + Recent_Entry** reloaded = Recent_parse(recent_path, test_dir, &loaded_count); + TEST_ASSERT_NOT_NULL(reloaded); + TEST_ASSERT_EQUAL(3, loaded_count); + + // Verify new order (loaded entries have relative paths) + TEST_ASSERT_EQUAL_STRING("/Roms/NES/game3.nes", reloaded[0]->path); + TEST_ASSERT_EQUAL_STRING("Game Three (Updated)", reloaded[0]->alias); + + // Cleanup + Recent_freeEntries(entries, 3); + Recent_freeEntries(loaded, 3); + Recent_freeEntries(modified, 3); + Recent_freeEntries(reloaded, 3); +} + +/////////////////////////////// +// MinArch Save File Workflows +/////////////////////////////// + +/** + * Integration test: Save state path generation + binary file I/O + * + * Workflow: + * 1. Generate save state paths using MinArch_getSaveStatePath + * 2. Write save state data using BinaryFile_write + * 3. Read back using BinaryFile_read + * 4. Verify data integrity across path generation and file I/O + */ +void test_minarch_save_state_workflow(void) { + char path[512]; + char save_path[256]; + char states_dir[512]; + + // Setup ROM + snprintf(path, sizeof(path), "%s/Roms/GB/mario.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Generate save state path for slot 0 + snprintf(states_dir, sizeof(states_dir), "%s/.userdata/miyoomini/gambatte", + test_dir); + MinArch_getStatePath(save_path, states_dir, "mario", 0); + + // Verify path format + TEST_ASSERT_TRUE(strstr(save_path, ".st0") != NULL); + TEST_ASSERT_TRUE(strstr(save_path, "gambatte") != NULL); + + // Create parent directory for save file + TEST_ASSERT_TRUE(create_parent_dir(save_path)); + + // Write save state data + unsigned char save_data[1024]; + for (int i = 0; i < 1024; i++) { + save_data[i] = i % 256; + } + + int written = BinaryFile_write(save_path, save_data, 1024); + TEST_ASSERT_EQUAL(1024, written); + + // Read back and verify + unsigned char read_data[1024]; + int read = BinaryFile_read(save_path, read_data, 1024); + TEST_ASSERT_EQUAL(1024, read); + + // Verify data integrity + for (int i = 0; i < 1024; i++) { + TEST_ASSERT_EQUAL(save_data[i], read_data[i]); + } +} + +/** + * Integration test: SRAM and RTC file handling + * + * Workflow: + * 1. Generate SRAM and RTC paths + * 2. Write data to both files + * 3. Verify both exist and contain correct data + * 4. Test MinArch save file integration + */ +void test_minarch_sram_rtc_workflow(void) { + char sram_path[256]; + char rtc_path[256]; + char saves_dir[512]; + + // Setup save directory + snprintf(saves_dir, sizeof(saves_dir), "%s/.userdata/miyoomini/gambatte", + test_dir); + + // Generate SRAM path + MinArch_getSRAMPath(sram_path, saves_dir, "pokemon"); + TEST_ASSERT_TRUE(strstr(sram_path, ".sav") != NULL); + + // Generate RTC path + MinArch_getRTCPath(rtc_path, saves_dir, "pokemon"); + TEST_ASSERT_TRUE(strstr(rtc_path, ".rtc") != NULL); + + // Create parent directories + TEST_ASSERT_TRUE(create_parent_dir(sram_path)); + TEST_ASSERT_TRUE(create_parent_dir(rtc_path)); + + // Write SRAM data (32KB like Game Boy) + unsigned char sram[32768]; + memset(sram, 0xFF, sizeof(sram)); + sram[0] = 0xAB; // Header byte + sram[1] = 0xCD; + + int written = BinaryFile_write(sram_path, sram, sizeof(sram)); + TEST_ASSERT_EQUAL(32768, written); + + // Write RTC data (8 bytes) + unsigned char rtc[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + written = BinaryFile_write(rtc_path, rtc, 8); + TEST_ASSERT_EQUAL(8, written); + + // Read back and verify + unsigned char sram_read[32768]; + int read = BinaryFile_read(sram_path, sram_read, sizeof(sram_read)); + TEST_ASSERT_EQUAL(32768, read); + TEST_ASSERT_EQUAL(0xAB, sram_read[0]); + TEST_ASSERT_EQUAL(0xCD, sram_read[1]); + + unsigned char rtc_read[8]; + read = BinaryFile_read(rtc_path, rtc_read, 8); + TEST_ASSERT_EQUAL(8, read); + TEST_ASSERT_EQUAL_MEMORY(rtc, rtc_read, 8); +} + +/////////////////////////////// +// Collection + M3U Integration +/////////////////////////////// + +/** + * Integration test: Collection containing M3U files with aliases + * + * Workflow: + * 1. Create collection with multi-disc games + * 2. Each game has M3U and map.txt + * 3. Parse collection + * 4. Verify M3U detection works for collection entries + * 5. Verify aliases apply correctly + */ +void test_collection_with_m3u_games(void) { + char path[512]; + + // Create first multi-disc game (FF7) + snprintf(path, sizeof(path), "%s/Roms/PS1/FF7/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/FF7/disc2.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + const char* ff7_discs[] = {"FF7/disc1.bin", "FF7/disc2.bin"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/FF7.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, ff7_discs, 2)); + + // Create second multi-disc game (MGS) + snprintf(path, sizeof(path), "%s/Roms/PS1/MGS/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/MGS/disc2.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + const char* mgs_discs[] = {"MGS/disc1.bin", "MGS/disc2.bin"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/MGS.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, mgs_discs, 2)); + + // Create map.txt with aliases + const char* rom_names[] = {"FF7.m3u", "MGS.m3u"}; + const char* aliases[] = {"Final Fantasy VII", "Metal Gear Solid"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, rom_names, aliases, 2)); + + // Create collection + const char* collection_paths[] = {"/Roms/PS1/FF7.m3u", "/Roms/PS1/MGS.m3u"}; + snprintf(path, sizeof(path), "%s/Collections/MultiDisc.txt", test_dir); + TEST_ASSERT_TRUE(create_test_collection(path, collection_paths, 2)); + + // Parse collection + int entry_count = 0; + Collection_Entry** entries = Collection_parse(path, test_dir, &entry_count); + TEST_ASSERT_NOT_NULL(entries); + TEST_ASSERT_EQUAL(2, entry_count); + + // Verify entries are M3U files (check extension) + TEST_ASSERT_TRUE(strstr(entries[0]->path, ".m3u") != NULL); + TEST_ASSERT_TRUE(strstr(entries[1]->path, ".m3u") != NULL); + + // Parse the M3U and verify discs + int disc_count = 0; + M3U_Disc** discs = M3U_getAllDiscs(entries[0]->path, &disc_count); + TEST_ASSERT_NOT_NULL(discs); + TEST_ASSERT_EQUAL(2, disc_count); + + // Verify map aliases work + char alias[256]; + strcpy(alias, ""); + Map_getAlias(entries[0]->path, alias); + TEST_ASSERT_EQUAL_STRING("Final Fantasy VII", alias); + + strcpy(alias, ""); + Map_getAlias(entries[1]->path, alias); + TEST_ASSERT_EQUAL_STRING("Metal Gear Solid", alias); + + // Cleanup + M3U_freeDiscs(discs, disc_count); + Collection_freeEntries(entries, entry_count); +} + +/////////////////////////////// +// File Detection Integration +/////////////////////////////// + +/** + * Integration test: All file detection utilities working together + * + * Workflow: + * 1. Create complex ROM directory with mixed content + * 2. Test hasEmu, hasM3u, hasCue, hasNonHiddenFiles together + * 3. Verify correct detection of all file types + */ +void test_file_detection_integration(void) { + char path[512]; + + // Create emulator pak (shared location) + snprintf(path, sizeof(path), "%s/Emus/PCSX.pak/launch.sh", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Also create in Paks directory for detection + snprintf(path, sizeof(path), "%s/Paks/Emus/PCSX.pak/launch.sh", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Create multi-disc game structure + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/Game.cue", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + const char* disc_files[] = {"Game/disc1.bin"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/Game.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, disc_files, 1)); + + // Create hidden files in directory + snprintf(path, sizeof(path), "%s/Roms/PS1/.DS_Store", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/PS1/.hidden", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Test emulator detection + char paks_path[512]; + snprintf(paks_path, sizeof(paks_path), "%s/Paks", test_dir); + int has_emu = MinUI_hasEmu("PCSX", paks_path, test_dir, "miyoomini"); + TEST_ASSERT_TRUE(has_emu); + + // Test M3U detection + char rom_path[512]; + snprintf(rom_path, sizeof(rom_path), "%s/Roms/PS1/Game/disc1.bin", test_dir); + char m3u_path[512]; + has_emu = MinUI_hasM3u(rom_path, m3u_path); + TEST_ASSERT_TRUE(has_emu); + + // Test CUE detection + char dir_path[512]; + snprintf(dir_path, sizeof(dir_path), "%s/Roms/PS1/Game", test_dir); + char cue_path[512]; + int has_cue = MinUI_hasCue(dir_path, cue_path); + TEST_ASSERT_TRUE(has_cue); + + // Test hasNonHiddenFiles (should see ROM files, not hidden files) + snprintf(dir_path, sizeof(dir_path), "%s/Roms/PS1", test_dir); + int has_files = MinUI_hasNonHiddenFiles(dir_path); + TEST_ASSERT_TRUE(has_files); + + // Test directory with ONLY hidden files + snprintf(path, sizeof(path), "%s/Roms/Empty/.DS_Store", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(dir_path, sizeof(dir_path), "%s/Roms/Empty", test_dir); + has_files = MinUI_hasNonHiddenFiles(dir_path); + TEST_ASSERT_FALSE(has_files); +} + +/////////////////////////////// +// Error Handling Integration +/////////////////////////////// + +/** + * Integration test: Error handling across multiple modules + * + * Workflow: + * 1. Test with missing files, empty files, invalid data + * 2. Verify modules handle errors gracefully when integrated + */ +void test_error_handling_integration(void) { + char path[512]; + + // Test 1: Collection with all missing ROMs + const char* missing_paths[] = {"/Roms/GB/missing1.gb", "/Roms/GB/missing2.gb"}; + snprintf(path, sizeof(path), "%s/Collections/Missing.txt", test_dir); + TEST_ASSERT_TRUE(create_test_collection(path, missing_paths, 2)); + + int entry_count = 0; + Collection_Entry** entries = Collection_parse(path, test_dir, &entry_count); + TEST_ASSERT_EQUAL(0, entry_count); // All ROMs missing, so empty + Collection_freeEntries(entries, entry_count); + + // Test 2: Recent.txt with missing ROMs + Recent_Entry** recent_entries = malloc(2 * sizeof(Recent_Entry*)); + recent_entries[0] = malloc(sizeof(Recent_Entry)); + recent_entries[0]->path = strdup("/Roms/GB/exists.gb"); + recent_entries[0]->alias = strdup("Good Game"); + + recent_entries[1] = malloc(sizeof(Recent_Entry)); + recent_entries[1]->path = strdup("/Roms/GB/missing.gb"); + recent_entries[1]->alias = strdup("Missing Game"); + + // Create only the first ROM + snprintf(path, sizeof(path), "%s/Roms/GB/exists.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Save and load + char recent_path[512]; + snprintf(recent_path, sizeof(recent_path), "%s/.userdata/.minui/recent.txt", + test_dir); + int saved = Recent_save(recent_path, recent_entries, 2); + TEST_ASSERT_TRUE(saved); + + int loaded_count = 0; + Recent_Entry** loaded = Recent_parse(recent_path, test_dir, &loaded_count); + TEST_ASSERT_NOT_NULL(loaded); + TEST_ASSERT_EQUAL(1, loaded_count); // Only one ROM exists + TEST_ASSERT_EQUAL_STRING("/Roms/GB/exists.gb", loaded[0]->path); + + // Cleanup + Recent_freeEntries(recent_entries, 2); + Recent_freeEntries(loaded, loaded_count); + + // Test 3: M3U with all missing discs + const char* missing_discs[] = {"missing1.bin", "missing2.bin"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/BadGame.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, missing_discs, 2)); + + int disc_count = 0; + M3U_Disc** discs = M3U_getAllDiscs(path, &disc_count); + TEST_ASSERT_EQUAL(0, disc_count); // All discs missing + M3U_freeDiscs(discs, disc_count); +} + +/////////////////////////////// +// Complex Multi-System Workflows +/////////////////////////////// + +/** + * Integration test: Collection spanning multiple systems with mixed features + * + * Workflow: + * 1. Create collection with ROMs from GB, NES, PS1 + * 2. Include single-disc games, multi-disc games, and games with aliases + * 3. Test all modules working together: Collection + M3U + Map + File detection + */ +void test_multi_system_collection_workflow(void) { + char path[512]; + + // GB: Simple ROM + snprintf(path, sizeof(path), "%s/Roms/GB/tetris.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // NES: ROM with alias + snprintf(path, sizeof(path), "%s/Roms/NES/smb.nes", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + const char* nes_names[] = {"smb.nes"}; + const char* nes_aliases[] = {"Super Mario Bros."}; + snprintf(path, sizeof(path), "%s/Roms/NES/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, nes_names, nes_aliases, 1)); + + // PS1: Multi-disc game with alias + snprintf(path, sizeof(path), "%s/Roms/PS1/RE2/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/RE2/disc2.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + const char* re2_discs[] = {"RE2/disc1.bin", "RE2/disc2.bin"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/RE2.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, re2_discs, 2)); + + const char* ps1_names[] = {"RE2.m3u"}; + const char* ps1_aliases[] = {"Resident Evil 2"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, ps1_names, ps1_aliases, 1)); + + // Create collection with all three + const char* collection_paths[] = {"/Roms/GB/tetris.gb", "/Roms/NES/smb.nes", + "/Roms/PS1/RE2.m3u"}; + snprintf(path, sizeof(path), "%s/Collections/Best.txt", test_dir); + TEST_ASSERT_TRUE(create_test_collection(path, collection_paths, 3)); + + // Parse collection + int entry_count = 0; + Collection_Entry** entries = Collection_parse(path, test_dir, &entry_count); + TEST_ASSERT_NOT_NULL(entries); + TEST_ASSERT_EQUAL(3, entry_count); + + // Verify each entry + char alias[256]; + + // Tetris: no alias + strcpy(alias, "Original"); + Map_getAlias(entries[0]->path, alias); + TEST_ASSERT_EQUAL_STRING("Original", alias); // Unchanged + + // SMB: has alias + strcpy(alias, ""); + Map_getAlias(entries[1]->path, alias); + TEST_ASSERT_EQUAL_STRING("Super Mario Bros.", alias); + + // RE2: has alias and M3U + strcpy(alias, ""); + Map_getAlias(entries[2]->path, alias); + TEST_ASSERT_EQUAL_STRING("Resident Evil 2", alias); + + // Verify it's an M3U file + TEST_ASSERT_TRUE(strstr(entries[2]->path, ".m3u") != NULL); + + // Parse M3U and verify discs + int disc_count = 0; + M3U_Disc** discs = M3U_getAllDiscs(entries[2]->path, &disc_count); + TEST_ASSERT_EQUAL(2, disc_count); + + // Cleanup + M3U_freeDiscs(discs, disc_count); + Collection_freeEntries(entries, entry_count); +} + +/** + * Integration test: Recent games with save states + * + * Workflow: + * 1. Create ROM and save state + * 2. Add to recent games + * 3. Verify save state exists for recent game + * 4. Test cross-module file verification + */ +void test_recent_with_save_states(void) { + char path[512]; + + // Create ROM + snprintf(path, sizeof(path), "%s/Roms/GB/game.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Generate save state path + char save_path[256]; + char states_dir[512]; + snprintf(states_dir, sizeof(states_dir), "%s/.userdata/miyoomini/gambatte", + test_dir); + MinArch_getStatePath(save_path, states_dir, "game", 0); + + // Create parent directory + TEST_ASSERT_TRUE(create_parent_dir(save_path)); + + // Write save state + unsigned char save_data[512]; + memset(save_data, 0x42, sizeof(save_data)); + int written = BinaryFile_write(save_path, save_data, sizeof(save_data)); + TEST_ASSERT_EQUAL(512, written); + + // Add to recent games + Recent_Entry** entries = malloc(sizeof(Recent_Entry*)); + entries[0] = malloc(sizeof(Recent_Entry)); + entries[0]->path = strdup("/Roms/GB/game.gb"); + entries[0]->alias = strdup("My Game"); + + char recent_path[512]; + snprintf(recent_path, sizeof(recent_path), "%s/.userdata/.minui/recent.txt", + test_dir); + int saved = Recent_save(recent_path, entries, 1); + TEST_ASSERT_TRUE(saved); + + // Load recent games + int loaded_count = 0; + Recent_Entry** loaded = Recent_parse(recent_path, test_dir, &loaded_count); + TEST_ASSERT_EQUAL(1, loaded_count); + + // Verify save state path matches what we used earlier + char verify_save_path[256]; + MinArch_getStatePath(verify_save_path, states_dir, "game", 0); + TEST_ASSERT_EQUAL_STRING(save_path, verify_save_path); + + // Verify can read save state data + unsigned char read_data[512]; + int read = BinaryFile_read(save_path, read_data, sizeof(read_data)); + TEST_ASSERT_EQUAL(512, read); + TEST_ASSERT_EQUAL(0x42, read_data[0]); + + // Cleanup + Recent_freeEntries(entries, 1); + Recent_freeEntries(loaded, loaded_count); +} + +/////////////////////////////// +// Config File Workflows +/////////////////////////////// + +/** + * Integration test: MinArch config file path generation + file I/O + * + * Workflow: + * 1. Generate game-specific config path + * 2. Write config data + * 3. Generate global config path + * 4. Verify both configs can coexist + */ +void test_minarch_config_file_integration(void) { + char game_cfg[256]; + char global_cfg[256]; + char config_dir[512]; + + snprintf(config_dir, sizeof(config_dir), "%s/.userdata/miyoomini/gpsp", + test_dir); + + // Generate game-specific config + MinArch_getConfigPath(game_cfg, config_dir, "Pokemon", NULL); + TEST_ASSERT_TRUE(strstr(game_cfg, "Pokemon.cfg") != NULL); + + // Generate global config + MinArch_getConfigPath(global_cfg, config_dir, NULL, NULL); + TEST_ASSERT_TRUE(strstr(global_cfg, "minarch.cfg") != NULL); + + // Verify they're different + TEST_ASSERT_NOT_EQUAL(strcmp(game_cfg, global_cfg), 0); + + // Create parent dir + TEST_ASSERT_TRUE(create_parent_dir(game_cfg)); + + // Write config data + const char* game_config = "frameskip=0\nvolume=80\n"; + const char* global_config = "show_fps=1\nauto_save=1\n"; + + FILE* f = fopen(game_cfg, "w"); + TEST_ASSERT_NOT_NULL(f); + fputs(game_config, f); + fclose(f); + + f = fopen(global_cfg, "w"); + TEST_ASSERT_NOT_NULL(f); + fputs(global_config, f); + fclose(f); + + // Verify both exist + f = fopen(game_cfg, "r"); + TEST_ASSERT_NOT_NULL(f); + fclose(f); + + f = fopen(global_cfg, "r"); + TEST_ASSERT_NOT_NULL(f); + fclose(f); +} + +/** + * Integration test: Device-specific config tags + * + * Workflow: + * 1. Generate configs for different devices + * 2. Verify device tags applied correctly + * 3. Test cross-device config isolation + */ +void test_config_device_tags(void) { + char miyoo_cfg[256]; + char rg35_cfg[256]; + char config_dir[512]; + + snprintf(config_dir, sizeof(config_dir), "%s/.userdata/shared/gpsp", test_dir); + + // Generate miyoomini config + MinArch_getConfigPath(miyoo_cfg, config_dir, "Game", "miyoomini"); + TEST_ASSERT_TRUE(strstr(miyoo_cfg, "-miyoomini.cfg") != NULL); + + // Generate rg35xx config + MinArch_getConfigPath(rg35_cfg, config_dir, "Game", "rg35xx"); + TEST_ASSERT_TRUE(strstr(rg35_cfg, "-rg35xx.cfg") != NULL); + + // Verify they're different + TEST_ASSERT_NOT_EQUAL(strcmp(miyoo_cfg, rg35_cfg), 0); + + // Same game, different devices should have different configs + TEST_ASSERT_TRUE(strstr(miyoo_cfg, "Game-miyoomini") != NULL); + TEST_ASSERT_TRUE(strstr(rg35_cfg, "Game-rg35xx") != NULL); +} + +/////////////////////////////// +// Auto-Resume Workflows +/////////////////////////////// + +/** + * Integration test: Auto-resume with slot 9 + * + * Workflow: + * 1. Create save state on slot 9 (auto-resume slot) + * 2. Add game to recent list + * 3. Verify resume capability + */ +void test_auto_resume_slot_9(void) { + char save_path[256]; + char states_dir[512]; + + // Create ROM + char rom_path[512]; + snprintf(rom_path, sizeof(rom_path), "%s/Roms/GB/zelda.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(rom_path)); + + // Generate slot 9 save state (auto-resume) + snprintf(states_dir, sizeof(states_dir), "%s/.userdata/miyoomini/gambatte", + test_dir); + MinArch_getStatePath(save_path, states_dir, "zelda", 9); + + TEST_ASSERT_TRUE(strstr(save_path, ".st9") != NULL); + + // Create save state + TEST_ASSERT_TRUE(create_parent_dir(save_path)); + unsigned char save_data[256]; + memset(save_data, 0x99, sizeof(save_data)); + int written = BinaryFile_write(save_path, save_data, sizeof(save_data)); + TEST_ASSERT_EQUAL(256, written); + + // Add to recent games + Recent_Entry** entries = malloc(sizeof(Recent_Entry*)); + entries[0] = malloc(sizeof(Recent_Entry)); + entries[0]->path = strdup("/Roms/GB/zelda.gb"); + entries[0]->alias = strdup("The Legend of Zelda"); + + char recent_path[512]; + snprintf(recent_path, sizeof(recent_path), "%s/.userdata/.minui/recent.txt", + test_dir); + int saved = Recent_save(recent_path, entries, 1); + TEST_ASSERT_TRUE(saved); + + // Load and verify + int count = 0; + Recent_Entry** loaded = Recent_parse(recent_path, test_dir, &count); + TEST_ASSERT_EQUAL(1, count); + + // Verify slot 9 save exists for recent game (could enable resume) + char verify_path[256]; + char full_rom[512]; + snprintf(full_rom, sizeof(full_rom), "%s%s", test_dir, loaded[0]->path); + MinArch_getStatePath(verify_path, states_dir, "zelda", 9); + TEST_ASSERT_EQUAL_STRING(save_path, verify_path); + + // Verify save data is intact + unsigned char read_data[256]; + int read = BinaryFile_read(save_path, read_data, sizeof(read_data)); + TEST_ASSERT_EQUAL(256, read); + TEST_ASSERT_EQUAL(0x99, read_data[0]); + + Recent_freeEntries(entries, 1); + Recent_freeEntries(loaded, count); +} + +/** + * Integration test: All 10 save state slots + * + * Workflow: + * 1. Create save states for all slots (0-9) + * 2. Verify all can be written and read + * 3. Test slot isolation + */ +void test_all_save_slots(void) { + char states_dir[512]; + snprintf(states_dir, sizeof(states_dir), "%s/.userdata/miyoomini/snes9x", + test_dir); + + // Create saves for all 10 slots + for (int slot = 0; slot < 10; slot++) { + char save_path[256]; + MinArch_getStatePath(save_path, states_dir, "metroid", slot); + + // Verify slot number in filename + char expected[10]; + snprintf(expected, sizeof(expected), ".st%d", slot); + TEST_ASSERT_TRUE(strstr(save_path, expected) != NULL); + + // Write unique data per slot + TEST_ASSERT_TRUE(create_parent_dir(save_path)); + unsigned char data[64]; + memset(data, slot, sizeof(data)); + + int written = BinaryFile_write(save_path, data, sizeof(data)); + TEST_ASSERT_EQUAL(64, written); + } + + // Read back and verify each slot has correct data + for (int slot = 0; slot < 10; slot++) { + char save_path[256]; + MinArch_getStatePath(save_path, states_dir, "metroid", slot); + + unsigned char data[64]; + int read = BinaryFile_read(save_path, data, sizeof(data)); + TEST_ASSERT_EQUAL(64, read); + TEST_ASSERT_EQUAL(slot, data[0]); + TEST_ASSERT_EQUAL(slot, data[63]); + } +} + +/////////////////////////////// +// Hidden ROM Workflows +/////////////////////////////// + +/** + * Integration test: Hidden ROMs in map.txt filtered from collection + * + * Workflow: + * 1. Create collection with 3 ROMs + * 2. One ROM has alias starting with '.' (hidden) + * 3. Verify hidden ROM handling in integration + */ +void test_hidden_roms_in_map(void) { + char path[512]; + + // Create ROMs + snprintf(path, sizeof(path), "%s/Roms/GB/good.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/GB/hidden.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/GB/another.gb", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Create map with one hidden ROM + const char* rom_names[] = {"good.gb", "hidden.gb", "another.gb"}; + const char* aliases[] = {"Good Game", ".Hidden Game", "Another Game"}; + snprintf(path, sizeof(path), "%s/Roms/GB/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, rom_names, aliases, 3)); + + // Get aliases and verify + char alias[256]; + char rom_path[512]; + + // Good ROM + snprintf(rom_path, sizeof(rom_path), "%s/Roms/GB/good.gb", test_dir); + strcpy(alias, ""); + Map_getAlias(rom_path, alias); + TEST_ASSERT_EQUAL_STRING("Good Game", alias); + + // Hidden ROM - alias starts with '.' + snprintf(rom_path, sizeof(rom_path), "%s/Roms/GB/hidden.gb", test_dir); + strcpy(alias, ""); + Map_getAlias(rom_path, alias); + TEST_ASSERT_EQUAL_STRING(".Hidden Game", alias); + TEST_ASSERT_EQUAL('.', alias[0]); // Verify starts with dot + + // Another ROM + snprintf(rom_path, sizeof(rom_path), "%s/Roms/GB/another.gb", test_dir); + strcpy(alias, ""); + Map_getAlias(rom_path, alias); + TEST_ASSERT_EQUAL_STRING("Another Game", alias); +} + +/////////////////////////////// +// Advanced Multi-Disc Scenarios +/////////////////////////////// + +/** + * Integration test: M3U getFirstDisc vs getAllDiscs consistency + * + * Workflow: + * 1. Create M3U with multiple discs + * 2. Get first disc with M3U_getFirstDisc + * 3. Get all discs with M3U_getAllDiscs + * 4. Verify first disc from both methods matches + */ +void test_m3u_first_vs_all_consistency(void) { + char path[512]; + + // Create multi-disc game + snprintf(path, sizeof(path), "%s/Roms/PS1/GT2/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/GT2/disc2.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + const char* discs[] = {"GT2/disc1.bin", "GT2/disc2.bin"}; + char m3u_path[512]; + snprintf(m3u_path, sizeof(m3u_path), "%s/Roms/PS1/GT2.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(m3u_path, discs, 2)); + + // Get first disc + char first_disc[256]; + int found = M3U_getFirstDisc(m3u_path, first_disc); + TEST_ASSERT_TRUE(found); + + // Get all discs + int disc_count = 0; + M3U_Disc** all_discs = M3U_getAllDiscs(m3u_path, &disc_count); + TEST_ASSERT_EQUAL(2, disc_count); + + // Verify first disc matches + TEST_ASSERT_EQUAL_STRING(first_disc, all_discs[0]->path); + + M3U_freeDiscs(all_discs, disc_count); +} + +/** + * Integration test: Nested game directory structures + * + * Workflow: + * 1. Create deeply nested ROM structure + * 2. Test M3U detection in nested dirs + * 3. Test path handling across modules + */ +void test_nested_directories(void) { + char path[512]; + + // Create nested structure: Roms/PS1/Games/Action/FF7/disc1.bin + snprintf(path, sizeof(path), "%s/Roms/PS1/Games/Action/FF7/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/PS1/Games/Action/FF7/disc2.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // M3U at Action level, disc paths relative to M3U location + const char* discs[] = {"FF7/disc1.bin", "FF7/disc2.bin"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/Games/Action/FF7.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(path, discs, 2)); + + // Parse M3U + int disc_count = 0; + M3U_Disc** m3u_discs = M3U_getAllDiscs(path, &disc_count); + TEST_ASSERT_EQUAL(2, disc_count); + + // Verify paths constructed correctly (relative to M3U location) + TEST_ASSERT_TRUE(strstr(m3u_discs[0]->path, "FF7/disc1.bin") != NULL); + TEST_ASSERT_TRUE(strstr(m3u_discs[1]->path, "FF7/disc2.bin") != NULL); + + M3U_freeDiscs(m3u_discs, disc_count); +} + +/////////////////////////////// +// Directory Utilities Advanced Integration +/////////////////////////////// + +/** + * Integration test: Empty directory with collection + * + * Workflow: + * 1. Create collection pointing to empty directory + * 2. Verify hasNonHiddenFiles returns false + * 3. Test collection parser handles this gracefully + */ +void test_empty_directory_collection(void) { + char path[512]; + + // Create empty directory (create a temp file then remove it to create the dir) + snprintf(path, sizeof(path), "%s/Roms/N64/.tmp", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + unlink(path); // Remove the file, leaving empty directory + + // Verify it's empty + snprintf(path, sizeof(path), "%s/Roms/N64", test_dir); + int has_files = MinUI_hasNonHiddenFiles(path); + TEST_ASSERT_FALSE(has_files); + + // Create collection pointing to non-existent ROMs in empty dir + const char* missing[] = {"/Roms/N64/game1.z64", "/Roms/N64/game2.z64"}; + snprintf(path, sizeof(path), "%s/Collections/N64Games.txt", test_dir); + TEST_ASSERT_TRUE(create_test_collection(path, missing, 2)); + + // Parse should return 0 entries (all missing) + int count = 0; + Collection_Entry** entries = Collection_parse(path, test_dir, &count); + TEST_ASSERT_EQUAL(0, count); + Collection_freeEntries(entries, count); +} + +/////////////////////////////// +// Cross-Module Comprehensive Workflows +/////////////////////////////// + +/** + * Integration test: ROM with ALL features enabled + * + * Workflow: + * 1. Create multi-disc game with M3U + CUE + * 2. Add map.txt alias + * 3. Create save state + * 4. Add to recent games + * 5. Verify all features work together + */ +void test_rom_with_all_features(void) { + char path[512]; + + // Create multi-disc game + snprintf(path, sizeof(path), "%s/Roms/PS1/FF8/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/FF8/disc2.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/FF8/disc3.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/FF8/disc4.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Create M3U + const char* discs[] = {"FF8/disc1.bin", "FF8/disc2.bin", "FF8/disc3.bin", + "FF8/disc4.bin"}; + char m3u_path[512]; + snprintf(m3u_path, sizeof(m3u_path), "%s/Roms/PS1/FF8.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(m3u_path, discs, 4)); + + // Create CUE file + snprintf(path, sizeof(path), "%s/Roms/PS1/FF8/FF8.cue", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Create map.txt with alias + const char* names[] = {"FF8.m3u"}; + const char* aliases[] = {"Final Fantasy VIII"}; + snprintf(path, sizeof(path), "%s/Roms/PS1/map.txt", test_dir); + TEST_ASSERT_TRUE(create_test_map(path, names, aliases, 1)); + + // Create save state on slot 0 + char save_path[256]; + char states_dir[512]; + snprintf(states_dir, sizeof(states_dir), "%s/.userdata/miyoomini/pcsx", test_dir); + MinArch_getStatePath(save_path, states_dir, "FF8", 0); + TEST_ASSERT_TRUE(create_parent_dir(save_path)); + + unsigned char save[512]; + memset(save, 0xF8, sizeof(save)); + TEST_ASSERT_EQUAL(512, BinaryFile_write(save_path, save, sizeof(save))); + + // Create SRAM + char sram_path[256]; + MinArch_getSRAMPath(sram_path, states_dir, "FF8"); + TEST_ASSERT_TRUE(create_parent_dir(sram_path)); + + unsigned char sram[8192]; + memset(sram, 0x00, sizeof(sram)); + TEST_ASSERT_EQUAL(8192, BinaryFile_write(sram_path, sram, sizeof(sram))); + + // Add to recent + Recent_Entry** entries = malloc(sizeof(Recent_Entry*)); + entries[0] = malloc(sizeof(Recent_Entry)); + entries[0]->path = strdup("/Roms/PS1/FF8.m3u"); + entries[0]->alias = NULL; // Will get from map.txt + + char recent_path[512]; + snprintf(recent_path, sizeof(recent_path), "%s/.userdata/.minui/recent.txt", + test_dir); + TEST_ASSERT_TRUE(Recent_save(recent_path, entries, 1)); + + // NOW VERIFY ALL FEATURES WORK TOGETHER + + // 1. M3U parsing + int disc_count = 0; + M3U_Disc** m3u_discs = M3U_getAllDiscs(m3u_path, &disc_count); + TEST_ASSERT_EQUAL(4, disc_count); + TEST_ASSERT_EQUAL_STRING("Disc 1", m3u_discs[0]->name); + TEST_ASSERT_EQUAL_STRING("Disc 4", m3u_discs[3]->name); + + // 2. Map alias + char alias[256]; + strcpy(alias, ""); + Map_getAlias(m3u_path, alias); + TEST_ASSERT_EQUAL_STRING("Final Fantasy VIII", alias); + + // 3. CUE detection + char dir_path[512]; + snprintf(dir_path, sizeof(dir_path), "%s/Roms/PS1/FF8", test_dir); + char cue_path[512]; + TEST_ASSERT_TRUE(MinUI_hasCue(dir_path, cue_path)); + + // 4. Recent games + int count = 0; + Recent_Entry** loaded = Recent_parse(recent_path, test_dir, &count); + TEST_ASSERT_EQUAL(1, count); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF8.m3u", loaded[0]->path); + + // 5. Save states exist + char verify_save[256]; + MinArch_getStatePath(verify_save, states_dir, "FF8", 0); + unsigned char verify_data[512]; + TEST_ASSERT_EQUAL(512, BinaryFile_read(verify_save, verify_data, sizeof(verify_data))); + TEST_ASSERT_EQUAL(0xF8, verify_data[0]); + + // 6. SRAM exists + char verify_sram[256]; + MinArch_getSRAMPath(verify_sram, states_dir, "FF8"); + unsigned char verify_sram_data[8192]; + TEST_ASSERT_EQUAL(8192, + BinaryFile_read(verify_sram, verify_sram_data, sizeof(verify_sram_data))); + + M3U_freeDiscs(m3u_discs, disc_count); + Recent_freeEntries(entries, 1); + Recent_freeEntries(loaded, count); +} + +/** + * Integration test: Multi-platform userdata structure + * + * Workflow: + * 1. Create save data for same ROM on different platforms + * 2. Verify data is isolated per platform + * 3. Test cross-platform compatibility + */ +void test_multi_platform_save_isolation(void) { + char miyoo_save[256]; + char rg35_save[256]; + char miyoo_dir[512]; + char rg35_dir[512]; + + // Generate paths for miyoomini + snprintf(miyoo_dir, sizeof(miyoo_dir), "%s/.userdata/miyoomini/gpsp", test_dir); + MinArch_getStatePath(miyoo_save, miyoo_dir, "pokemon", 0); + + // Generate paths for rg35xx + snprintf(rg35_dir, sizeof(rg35_dir), "%s/.userdata/rg35xx/gpsp", test_dir); + MinArch_getStatePath(rg35_save, rg35_dir, "pokemon", 0); + + // Verify they're different + TEST_ASSERT_NOT_EQUAL(strcmp(miyoo_save, rg35_save), 0); + TEST_ASSERT_TRUE(strstr(miyoo_save, "miyoomini") != NULL); + TEST_ASSERT_TRUE(strstr(rg35_save, "rg35xx") != NULL); + + // Create both saves with different data + TEST_ASSERT_TRUE(create_parent_dir(miyoo_save)); + TEST_ASSERT_TRUE(create_parent_dir(rg35_save)); + + unsigned char miyoo_data[128]; + memset(miyoo_data, 0xAA, sizeof(miyoo_data)); + TEST_ASSERT_EQUAL(128, BinaryFile_write(miyoo_save, miyoo_data, sizeof(miyoo_data))); + + unsigned char rg35_data[128]; + memset(rg35_data, 0xBB, sizeof(rg35_data)); + TEST_ASSERT_EQUAL(128, BinaryFile_write(rg35_save, rg35_data, sizeof(rg35_data))); + + // Read back and verify isolation + unsigned char read_miyoo[128]; + unsigned char read_rg35[128]; + + TEST_ASSERT_EQUAL(128, BinaryFile_read(miyoo_save, read_miyoo, sizeof(read_miyoo))); + TEST_ASSERT_EQUAL(128, BinaryFile_read(rg35_save, read_rg35, sizeof(read_rg35))); + + TEST_ASSERT_EQUAL(0xAA, read_miyoo[0]); + TEST_ASSERT_EQUAL(0xBB, read_rg35[0]); +} + +/** + * Integration test: M3U with individual CUE files per disc + * + * Workflow: + * 1. Create multi-disc game where each disc has its own CUE + * 2. Verify M3U parsing works + * 3. Verify CUE detection works alongside M3U + */ +void test_m3u_with_multiple_cues(void) { + char path[512]; + + // Create game with CUE per disc + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/disc1.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/disc1.cue", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/disc2.bin", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/disc2.cue", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Create M3U + const char* discs[] = {"Game/disc1.bin", "Game/disc2.bin"}; + char m3u_path[512]; + snprintf(m3u_path, sizeof(m3u_path), "%s/Roms/PS1/Game.m3u", test_dir); + TEST_ASSERT_TRUE(create_test_m3u(m3u_path, discs, 2)); + + // Also create directory-level CUE + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/Game.cue", test_dir); + TEST_ASSERT_TRUE(create_test_rom(path)); + + // Verify M3U parsing works + int disc_count = 0; + M3U_Disc** m3u_discs = M3U_getAllDiscs(m3u_path, &disc_count); + TEST_ASSERT_EQUAL(2, disc_count); + + // Verify CUE detection (directory-level) + char dir_path[512]; + snprintf(dir_path, sizeof(dir_path), "%s/Roms/PS1/Game", test_dir); + char cue_path[512]; + TEST_ASSERT_TRUE(MinUI_hasCue(dir_path, cue_path)); + + // Verify individual CUE files exist + snprintf(path, sizeof(path), "%s/Roms/PS1/Game/disc1.cue", test_dir); + FILE* f = fopen(path, "r"); + TEST_ASSERT_NOT_NULL(f); + fclose(f); + + M3U_freeDiscs(m3u_discs, disc_count); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // Multi-disc workflows + RUN_TEST(test_multi_disc_game_complete_workflow); + RUN_TEST(test_multi_disc_detection); + RUN_TEST(test_m3u_first_vs_all_consistency); + RUN_TEST(test_m3u_with_multiple_cues); + RUN_TEST(test_nested_directories); + + // Collection workflows + RUN_TEST(test_collection_with_aliases); + RUN_TEST(test_collection_with_m3u_games); + RUN_TEST(test_multi_system_collection_workflow); + RUN_TEST(test_empty_directory_collection); + + // Recent games workflows + RUN_TEST(test_recent_games_roundtrip); + RUN_TEST(test_recent_with_save_states); + + // MinArch save file workflows + RUN_TEST(test_minarch_save_state_workflow); + RUN_TEST(test_minarch_sram_rtc_workflow); + RUN_TEST(test_all_save_slots); + RUN_TEST(test_auto_resume_slot_9); + + // Config file workflows + RUN_TEST(test_minarch_config_file_integration); + RUN_TEST(test_config_device_tags); + + // File detection integration + RUN_TEST(test_file_detection_integration); + + // Hidden ROM workflows + RUN_TEST(test_hidden_roms_in_map); + + // Cross-module comprehensive workflows + RUN_TEST(test_rom_with_all_features); + RUN_TEST(test_multi_platform_save_isolation); + + // Error handling + RUN_TEST(test_error_handling_integration); + + return UNITY_END(); +} diff --git a/tests/sdl_mocking_example b/tests/sdl_mocking_example new file mode 100755 index 00000000..4a9efea0 Binary files /dev/null and b/tests/sdl_mocking_example differ diff --git a/tests/support/fff/fff.h b/tests/support/fff/fff.h new file mode 100644 index 00000000..cdb1c46d --- /dev/null +++ b/tests/support/fff/fff.h @@ -0,0 +1,6588 @@ +/* +LICENSE + +The MIT License (MIT) + +Copyright (c) 2010 Michael Long + +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. +*/ +#ifndef FAKE_FUNCTIONS +#define FAKE_FUNCTIONS + +#include +#include /* For memset and memcpy */ + +#define FFF_MAX_ARGS (20u) +#ifndef FFF_ARG_HISTORY_LEN + #define FFF_ARG_HISTORY_LEN (50u) +#endif +#ifndef FFF_CALL_HISTORY_LEN + #define FFF_CALL_HISTORY_LEN (50u) +#endif +#ifndef FFF_GCC_FUNCTION_ATTRIBUTES + #define FFF_GCC_FUNCTION_ATTRIBUTES +#endif +#ifndef CUSTOM_FFF_FUNCTION_TEMPLATE +#define CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN, FUNCNAME, ...) \ + RETURN(*FUNCNAME)(__VA_ARGS__) +#endif /* CUSTOM_FFF_FUNCTION_TEMPLATE */ +/* -- INTERNAL HELPER MACROS -- */ +#define SET_RETURN_SEQ(FUNCNAME, ARRAY_POINTER, ARRAY_LEN) \ + FUNCNAME##_fake.return_val_seq = ARRAY_POINTER; \ + FUNCNAME##_fake.return_val_seq_len = ARRAY_LEN; +#define SET_CUSTOM_FAKE_SEQ(FUNCNAME, ARRAY_POINTER, ARRAY_LEN) \ + FUNCNAME##_fake.custom_fake_seq = ARRAY_POINTER; \ + FUNCNAME##_fake.custom_fake_seq_len = ARRAY_LEN; + +/* Defining a function to reset a fake function */ +#define RESET_FAKE(FUNCNAME) { \ + FUNCNAME##_reset(); \ +} \ + + +#define DECLARE_ARG(type, n, FUNCNAME) \ + type arg##n##_val; \ + type arg##n##_history[FFF_ARG_HISTORY_LEN]; + +#define DECLARE_ALL_FUNC_COMMON \ + unsigned int call_count; \ + unsigned int arg_history_len; \ + unsigned int arg_histories_dropped; \ + +#define DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + RETURN_TYPE return_val_history[FFF_ARG_HISTORY_LEN]; + +#define SAVE_ARG(FUNCNAME, n) \ + memcpy((void*)&FUNCNAME##_fake.arg##n##_val, (void*)&arg##n, sizeof(arg##n)); + +#define ROOM_FOR_MORE_HISTORY(FUNCNAME) \ + FUNCNAME##_fake.call_count < FFF_ARG_HISTORY_LEN + +#define SAVE_RET_HISTORY(FUNCNAME, RETVAL) \ + if ((FUNCNAME##_fake.call_count - 1) < FFF_ARG_HISTORY_LEN) \ + memcpy((void *)&FUNCNAME##_fake.return_val_history[FUNCNAME##_fake.call_count - 1], (const void *) &RETVAL, sizeof(RETVAL)); \ + +#define SAVE_ARG_HISTORY(FUNCNAME, ARGN) \ + memcpy((void*)&FUNCNAME##_fake.arg##ARGN##_history[FUNCNAME##_fake.call_count], (void*)&arg##ARGN, sizeof(arg##ARGN)); + +#define HISTORY_DROPPED(FUNCNAME) \ + FUNCNAME##_fake.arg_histories_dropped++ + +#define DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE return_val; \ + int return_val_seq_len; \ + int return_val_seq_idx; \ + RETURN_TYPE * return_val_seq; \ + +#define DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + int custom_fake_seq_len; \ + int custom_fake_seq_idx; \ + +#define INCREMENT_CALL_COUNT(FUNCNAME) \ + FUNCNAME##_fake.call_count++ + +#define RETURN_FAKE_RESULT(FUNCNAME) \ + if (FUNCNAME##_fake.return_val_seq_len){ /* then its a sequence */ \ + if(FUNCNAME##_fake.return_val_seq_idx < FUNCNAME##_fake.return_val_seq_len) { \ + SAVE_RET_HISTORY(FUNCNAME, FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_idx]) \ + return FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_idx++]; \ + } \ + SAVE_RET_HISTORY(FUNCNAME, FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_len-1]) \ + return FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_len-1]; /* return last element */ \ + } \ + SAVE_RET_HISTORY(FUNCNAME, FUNCNAME##_fake.return_val) \ + return FUNCNAME##_fake.return_val; \ + +#ifdef __cplusplus + #define FFF_EXTERN_C extern "C"{ + #define FFF_END_EXTERN_C } +#else /* ansi c */ + #define FFF_EXTERN_C + #define FFF_END_EXTERN_C +#endif /* cpp/ansi c */ + +#define DEFINE_RESET_FUNCTION(FUNCNAME) \ + void FUNCNAME##_reset(void){ \ + memset((void*)&FUNCNAME##_fake, 0, sizeof(FUNCNAME##_fake) - sizeof(FUNCNAME##_fake.custom_fake) - sizeof(FUNCNAME##_fake.custom_fake_seq)); \ + FUNCNAME##_fake.custom_fake = NULL; \ + FUNCNAME##_fake.custom_fake_seq = NULL; \ + FUNCNAME##_fake.arg_history_len = FFF_ARG_HISTORY_LEN; \ + } +/* -- END INTERNAL HELPER MACROS -- */ + +typedef void (*fff_function_t)(void); +typedef struct { + fff_function_t call_history[FFF_CALL_HISTORY_LEN]; + unsigned int call_history_idx; +} fff_globals_t; + +FFF_EXTERN_C +extern fff_globals_t fff; +FFF_END_EXTERN_C + +#define DEFINE_FFF_GLOBALS \ + FFF_EXTERN_C \ + fff_globals_t fff; \ + FFF_END_EXTERN_C + +#define FFF_RESET_HISTORY() \ + fff.call_history_idx = 0; \ + memset(fff.call_history, 0, sizeof(fff.call_history)); + +#define REGISTER_CALL(function) \ + if(fff.call_history_idx < FFF_CALL_HISTORY_LEN) \ + fff.call_history[fff.call_history_idx++] = (fff_function_t)function; + +#define DECLARE_FAKE_VOID_FUNC0(FUNCNAME) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, void); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, void); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(void); \ + +#define DEFINE_FAKE_VOID_FUNC0(FUNCNAME) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(void){ \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC0(FUNCNAME) \ + DECLARE_FAKE_VOID_FUNC0(FUNCNAME) \ + DEFINE_FAKE_VOID_FUNC0(FUNCNAME) \ + + +#define DECLARE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0); \ + +#define DEFINE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + DECLARE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + DEFINE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1); \ + +#define DEFINE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DECLARE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DEFINE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2); \ + +#define DEFINE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DECLARE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DEFINE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3); \ + +#define DEFINE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DECLARE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DEFINE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4); \ + +#define DEFINE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DECLARE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DEFINE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5); \ + +#define DEFINE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DECLARE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DEFINE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6); \ + +#define DEFINE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DECLARE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DEFINE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7); \ + +#define DEFINE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DECLARE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DEFINE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8); \ + +#define DEFINE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DECLARE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DEFINE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9); \ + +#define DEFINE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DECLARE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DEFINE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10); \ + +#define DEFINE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DECLARE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DEFINE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11); \ + +#define DEFINE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DECLARE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DEFINE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12); \ + +#define DEFINE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DECLARE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DEFINE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13); \ + +#define DEFINE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DECLARE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DEFINE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14); \ + +#define DEFINE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DECLARE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DEFINE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15); \ + +#define DEFINE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DECLARE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DEFINE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16); \ + +#define DEFINE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DECLARE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DEFINE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17); \ + +#define DEFINE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DECLARE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DEFINE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18); \ + +#define DEFINE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DECLARE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DEFINE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ARG(ARG19_TYPE, 19, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19); \ + +#define DEFINE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + SAVE_ARG(FUNCNAME, 19); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + SAVE_ARG_HISTORY(FUNCNAME, 19); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + } \ + else{ \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DECLARE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DEFINE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, void); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, void); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(void); \ + +#define DEFINE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(void){ \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + DECLARE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + DEFINE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + + +#define DECLARE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0); \ + +#define DEFINE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + DECLARE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + DEFINE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1); \ + +#define DEFINE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DECLARE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DEFINE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2); \ + +#define DEFINE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DECLARE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DEFINE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3); \ + +#define DEFINE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DECLARE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DEFINE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4); \ + +#define DEFINE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DECLARE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DEFINE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5); \ + +#define DEFINE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DECLARE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DEFINE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6); \ + +#define DEFINE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DECLARE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DEFINE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7); \ + +#define DEFINE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DECLARE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DEFINE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8); \ + +#define DEFINE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DECLARE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DEFINE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9); \ + +#define DEFINE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DECLARE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DEFINE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10); \ + +#define DEFINE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DECLARE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DEFINE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11); \ + +#define DEFINE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DECLARE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DEFINE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12); \ + +#define DEFINE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DECLARE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DEFINE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13); \ + +#define DEFINE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DECLARE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DEFINE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14); \ + +#define DEFINE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DECLARE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DEFINE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15); \ + +#define DEFINE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DECLARE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DEFINE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16); \ + +#define DEFINE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DECLARE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DEFINE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17); \ + +#define DEFINE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DECLARE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DEFINE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18); \ + +#define DEFINE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DECLARE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DEFINE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ARG(ARG19_TYPE, 19, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19); \ + +#define DEFINE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + SAVE_ARG(FUNCNAME, 19); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + SAVE_ARG_HISTORY(FUNCNAME, 19); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + else{ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + } \ + if (FUNCNAME##_fake.custom_fake != NULL){ \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DECLARE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DEFINE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ...); \ + +#define DEFINE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg0); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg0); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg0); \ + FUNCNAME##_fake.custom_fake(arg0, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ...); \ + +#define DEFINE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg1); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg1); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg1); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ...); \ + +#define DEFINE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg2); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg2); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg2); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ...); \ + +#define DEFINE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg3); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg3); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg3); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ...); \ + +#define DEFINE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg4); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg4); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg4); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ...); \ + +#define DEFINE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg5); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg5); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg5); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ...); \ + +#define DEFINE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg6); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg6); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg6); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ...); \ + +#define DEFINE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg7); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg7); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg7); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ...); \ + +#define DEFINE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg8); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg8); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg8); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ...); \ + +#define DEFINE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg9); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg9); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg9); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ...); \ + +#define DEFINE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg10); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg10); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg10); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ...); \ + +#define DEFINE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg11); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg11); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg11); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ...); \ + +#define DEFINE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg12); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg12); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg12); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ...); \ + +#define DEFINE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg13); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg13); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg13); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ...); \ + +#define DEFINE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg14); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg14); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg14); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ...); \ + +#define DEFINE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg15); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg15); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg15); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ...); \ + +#define DEFINE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg16); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg16); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg16); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ...); \ + +#define DEFINE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg17); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg17); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg17); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(void, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ...); \ + +#define DEFINE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + void FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg18); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, ap); \ + va_end(ap); \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg18); \ + FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, ap); \ + va_end(ap); \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + va_list ap; \ + va_start(ap, arg18); \ + FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, ap); \ + va_end(ap); \ + } \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg0); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg0); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg0); \ + ret = FUNCNAME##_fake.custom_fake(arg0, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg1); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg1); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg1); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg2); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg2); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg2); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg3); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg3); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg3); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg4); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg4); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg4); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg5); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg5); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg5); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg6); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg6); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg6); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg7); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg7); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg7); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg8); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg8); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg8); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg9); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg9); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg9); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg10); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg10); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg10); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg11); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg11); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg11); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg12); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg12); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg12); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg13); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg13); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg13); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg14); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg14); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg14); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg15); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg15); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg15); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg16); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg16); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg16); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg17); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg17); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg17); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + DECLARE_RETURN_VALUE_HISTORY(RETURN_TYPE) \ + DECLARE_CUSTOM_FAKE_SEQ_VARIABLES \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, custom_fake, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, va_list ap); \ + CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN_TYPE, *custom_fake_seq, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, va_list ap); \ + } FUNCNAME##_Fake; \ + extern FUNCNAME##_Fake FUNCNAME##_fake; \ + void FUNCNAME##_reset(void); \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ...); \ + +#define DEFINE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + FUNCNAME##_Fake FUNCNAME##_fake; \ + RETURN_TYPE FFF_GCC_FUNCTION_ATTRIBUTES FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){ \ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + } \ + else{ \ + HISTORY_DROPPED(FUNCNAME); \ + } \ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake_seq_len){ /* a sequence of custom fakes */ \ + if (FUNCNAME##_fake.custom_fake_seq_idx < FUNCNAME##_fake.custom_fake_seq_len){ \ + va_list ap; \ + va_start(ap, arg18); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_idx++](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + else{ \ + va_list ap; \ + va_start(ap, arg18); \ + RETURN_TYPE ret = FUNCNAME##_fake.custom_fake_seq[FUNCNAME##_fake.custom_fake_seq_len-1](arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + va_end(ap); \ + return ret; \ + } \ + } \ + if(FUNCNAME##_fake.custom_fake){ \ + RETURN_TYPE ret; \ + va_list ap; \ + va_start(ap, arg18); \ + ret = FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, ap); \ + va_end(ap); \ + SAVE_RET_HISTORY(FUNCNAME, ret); \ + return ret; \ + } \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + +#define FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + +/* MSVC expand macro fix */ +#define EXPAND(x) x + +#define PP_NARG_MINUS2(...) EXPAND(PP_NARG_MINUS2_(__VA_ARGS__, PP_RSEQ_N_MINUS2())) + +#define PP_NARG_MINUS2_(...) EXPAND(PP_ARG_MINUS2_N(__VA_ARGS__)) + +#define PP_ARG_MINUS2_N(returnVal, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N + +#define PP_RSEQ_N_MINUS2() 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 + +#define PP_NARG_MINUS1(...) EXPAND(PP_NARG_MINUS1_(__VA_ARGS__, PP_RSEQ_N_MINUS1())) + +#define PP_NARG_MINUS1_(...) EXPAND(PP_ARG_MINUS1_N(__VA_ARGS__)) + +#define PP_ARG_MINUS1_N( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N + +#define PP_RSEQ_N_MINUS1() 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 + + + +/* DECLARE AND DEFINE FAKE FUNCTIONS - PLACE IN TEST FILES */ + +#define FAKE_VALUE_FUNC(...) EXPAND(FUNC_VALUE_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__)) + +#define FUNC_VALUE_(N,...) EXPAND(FUNC_VALUE_N(N,__VA_ARGS__)) + +#define FUNC_VALUE_N(N,...) EXPAND(FAKE_VALUE_FUNC ## N(__VA_ARGS__)) + + +#define FAKE_VOID_FUNC(...) EXPAND(FUNC_VOID_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__)) + +#define FUNC_VOID_(N,...) EXPAND(FUNC_VOID_N(N,__VA_ARGS__)) + +#define FUNC_VOID_N(N,...) EXPAND(FAKE_VOID_FUNC ## N(__VA_ARGS__)) + + +#define FAKE_VALUE_FUNC_VARARG(...) EXPAND(FUNC_VALUE_VARARG_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__)) + +#define FUNC_VALUE_VARARG_(N,...) EXPAND(FUNC_VALUE_VARARG_N(N,__VA_ARGS__)) + +#define FUNC_VALUE_VARARG_N(N,...) EXPAND(FAKE_VALUE_FUNC ## N ## _VARARG(__VA_ARGS__)) + + +#define FAKE_VOID_FUNC_VARARG(...) EXPAND(FUNC_VOID_VARARG_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__)) + +#define FUNC_VOID_VARARG_(N,...) EXPAND(FUNC_VOID_VARARG_N(N,__VA_ARGS__)) + +#define FUNC_VOID_VARARG_N(N,...) EXPAND(FAKE_VOID_FUNC ## N ## _VARARG(__VA_ARGS__)) + + + +/* DECLARE FAKE FUNCTIONS - PLACE IN HEADER FILES */ + +#define DECLARE_FAKE_VALUE_FUNC(...) EXPAND(DECLARE_FUNC_VALUE_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__)) + +#define DECLARE_FUNC_VALUE_(N,...) EXPAND(DECLARE_FUNC_VALUE_N(N,__VA_ARGS__)) + +#define DECLARE_FUNC_VALUE_N(N,...) EXPAND(DECLARE_FAKE_VALUE_FUNC ## N(__VA_ARGS__)) + + +#define DECLARE_FAKE_VOID_FUNC(...) EXPAND(DECLARE_FUNC_VOID_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__)) + +#define DECLARE_FUNC_VOID_(N,...) EXPAND(DECLARE_FUNC_VOID_N(N,__VA_ARGS__)) + +#define DECLARE_FUNC_VOID_N(N,...) EXPAND(DECLARE_FAKE_VOID_FUNC ## N(__VA_ARGS__)) + + +#define DECLARE_FAKE_VALUE_FUNC_VARARG(...) EXPAND(DECLARE_FUNC_VALUE_VARARG_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__)) + +#define DECLARE_FUNC_VALUE_VARARG_(N,...) EXPAND(DECLARE_FUNC_VALUE_VARARG_N(N,__VA_ARGS__)) + +#define DECLARE_FUNC_VALUE_VARARG_N(N,...) EXPAND(DECLARE_FAKE_VALUE_FUNC ## N ## _VARARG(__VA_ARGS__)) + + +#define DECLARE_FAKE_VOID_FUNC_VARARG(...) EXPAND(DECLARE_FUNC_VOID_VARARG_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__)) + +#define DECLARE_FUNC_VOID_VARARG_(N,...) EXPAND(DECLARE_FUNC_VOID_VARARG_N(N,__VA_ARGS__)) + +#define DECLARE_FUNC_VOID_VARARG_N(N,...) EXPAND(DECLARE_FAKE_VOID_FUNC ## N ## _VARARG(__VA_ARGS__)) + + + +/* DEFINE FAKE FUNCTIONS - PLACE IN SOURCE FILES */ + +#define DEFINE_FAKE_VALUE_FUNC(...) EXPAND(DEFINE_FUNC_VALUE_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__)) + +#define DEFINE_FUNC_VALUE_(N,...) EXPAND(DEFINE_FUNC_VALUE_N(N,__VA_ARGS__)) + +#define DEFINE_FUNC_VALUE_N(N,...) EXPAND(DEFINE_FAKE_VALUE_FUNC ## N(__VA_ARGS__)) + + +#define DEFINE_FAKE_VOID_FUNC(...) EXPAND(DEFINE_FUNC_VOID_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__)) + +#define DEFINE_FUNC_VOID_(N,...) EXPAND(DEFINE_FUNC_VOID_N(N,__VA_ARGS__)) + +#define DEFINE_FUNC_VOID_N(N,...) EXPAND(DEFINE_FAKE_VOID_FUNC ## N(__VA_ARGS__)) + + +#define DEFINE_FAKE_VALUE_FUNC_VARARG(...) EXPAND(DEFINE_FUNC_VALUE_VARARG_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__)) + +#define DEFINE_FUNC_VALUE_VARARG_(N,...) EXPAND(DEFINE_FUNC_VALUE_VARARG_N(N,__VA_ARGS__)) + +#define DEFINE_FUNC_VALUE_VARARG_N(N,...) EXPAND(DEFINE_FAKE_VALUE_FUNC ## N ## _VARARG(__VA_ARGS__)) + + +#define DEFINE_FAKE_VOID_FUNC_VARARG(...) EXPAND(DEFINE_FUNC_VOID_VARARG_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__)) + +#define DEFINE_FUNC_VOID_VARARG_(N,...) EXPAND(DEFINE_FUNC_VOID_VARARG_N(N,__VA_ARGS__)) + +#define DEFINE_FUNC_VOID_VARARG_N(N,...) EXPAND(DEFINE_FAKE_VOID_FUNC ## N ## _VARARG(__VA_ARGS__)) + + + + +#endif /* FAKE_FUNCTIONS */ diff --git a/tests/support/fs_mocks.c b/tests/support/fs_mocks.c new file mode 100644 index 00000000..190903c6 --- /dev/null +++ b/tests/support/fs_mocks.c @@ -0,0 +1,182 @@ +/** + * fs_mocks.c - File system mock implementation using GCC --wrap + * + * Implements an in-memory file system for unit testing. + * Intercepts exists(), fopen(), fclose(), fgets() via --wrap linker flag. + * + * Note: Requires GCC/GNU ld. Works in Docker, not on macOS with clang/ld64. + */ + +#include "fs_mocks.h" +#include +#include + +/////////////////////////////// +// Mock File System State +/////////////////////////////// + +#define MAX_MOCK_FILES 100 +#define MAX_MOCK_FILE_SIZE 8192 + +typedef struct MockFile { + char path[512]; + char content[MAX_MOCK_FILE_SIZE]; + int exists; +} MockFile; + +typedef struct MockFileHandle { + MockFile* file; + char* read_pos; // Current position for fgets() + int is_open; +} MockFileHandle; + +static struct { + MockFile files[MAX_MOCK_FILES]; + int file_count; + MockFileHandle handles[10]; // Up to 10 open files +} mock_fs = {0}; + +/////////////////////////////// +// Mock FS Control Functions +/////////////////////////////// + +void mock_fs_reset(void) { + memset(&mock_fs, 0, sizeof(mock_fs)); +} + +void mock_fs_add_file(const char* path, const char* content) { + if (mock_fs.file_count >= MAX_MOCK_FILES) + return; + + MockFile* file = &mock_fs.files[mock_fs.file_count]; + strncpy(file->path, path, sizeof(file->path) - 1); + strncpy(file->content, content, sizeof(file->content) - 1); + file->exists = 1; + + mock_fs.file_count++; +} + +int mock_fs_exists(const char* path) { + for (int i = 0; i < mock_fs.file_count; i++) { + if (strcmp(mock_fs.files[i].path, path) == 0) { + return mock_fs.files[i].exists; + } + } + return 0; +} + +/////////////////////////////// +// Helper Functions +/////////////////////////////// + +static MockFile* find_file(const char* path) { + for (int i = 0; i < mock_fs.file_count; i++) { + if (strcmp(mock_fs.files[i].path, path) == 0) { + return &mock_fs.files[i]; + } + } + return NULL; +} + +static MockFileHandle* allocate_handle(void) { + for (int i = 0; i < 10; i++) { + if (!mock_fs.handles[i].is_open) { + return &mock_fs.handles[i]; + } + } + return NULL; +} + +static MockFileHandle* find_handle(FILE* stream) { + for (int i = 0; i < 10; i++) { + if (mock_fs.handles[i].is_open && (FILE*)&mock_fs.handles[i] == stream) { + return &mock_fs.handles[i]; + } + } + return NULL; +} + +/////////////////////////////// +// Wrapped Function Implementations +/////////////////////////////// + +/** + * Wrapped exists() - checks mock file system + */ +int __wrap_exists(char* path) { + return mock_fs_exists(path); +} + +/** + * Wrapped fopen() - opens files from mock file system + */ +FILE* __wrap_fopen(const char* pathname, const char* mode) { + // Only support read mode + if (mode[0] != 'r') { + return NULL; // Write mode not supported (use real files in separate tests) + } + + MockFile* file = find_file(pathname); + if (!file) { + return NULL; // File not found + } + + MockFileHandle* handle = allocate_handle(); + if (!handle) { + return NULL; // Too many open files + } + + handle->file = file; + handle->read_pos = file->content; + handle->is_open = 1; + + // Return the handle address as FILE* + // This is a hack but works for testing + return (FILE*)handle; +} + +/** + * Wrapped fclose() - closes mock file handle + */ +int __wrap_fclose(FILE* stream) { + MockFileHandle* handle = find_handle(stream); + if (!handle) { + return EOF; + } + + handle->is_open = 0; + handle->file = NULL; + handle->read_pos = NULL; + + return 0; +} + +/** + * Wrapped fgets() - reads lines from mock file + */ +char* __wrap_fgets(char* s, int size, FILE* stream) { + MockFileHandle* handle = find_handle(stream); + if (!handle || !handle->read_pos) { + return NULL; + } + + // End of file? + if (*handle->read_pos == '\0') { + return NULL; + } + + // Read until newline or size limit + int i = 0; + while (i < size - 1 && *handle->read_pos != '\0') { + s[i] = *handle->read_pos; + handle->read_pos++; + i++; + + if (s[i - 1] == '\n') { + break; + } + } + + s[i] = '\0'; + return s; +} diff --git a/tests/support/fs_mocks.h b/tests/support/fs_mocks.h new file mode 100644 index 00000000..dc2370db --- /dev/null +++ b/tests/support/fs_mocks.h @@ -0,0 +1,83 @@ +/** + * fs_mocks.h - File system mocking for unit tests + * + * Provides mock implementations of file I/O using GCC's --wrap linker flag. + * This allows testing file-heavy code without actual file system operations. + * + * Uses GCC --wrap to intercept: + * - exists() - MinUI's file existence check + * - fopen/fclose - File handle operations (read mode only) + * - fgets - Line-by-line reading + * + * Usage in tests: + * mock_fs_reset(); + * mock_fs_add_file("/path/to/file.txt", "line 1\nline 2\n"); + * + * // Now exists("/path/to/file.txt") returns 1 + * // fopen("/path/to/file.txt", "r") returns mock FILE* + * // fgets() returns lines from the mock content + * + * Compilation: + * gcc ... -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets + * + * Note on write/directory/binary operations: + * For testing file writes (fputs, fprintf, fwrite), directory operations + * (opendir, readdir), or binary I/O, use real temp files instead of mocking. + * This is more reliable and works across all platforms. + * + * Note: Requires GCC/GNU ld (works in Docker, not on macOS with clang) + */ + +#ifndef FS_MOCKS_H +#define FS_MOCKS_H + +#include + +/////////////////////////////// +// Mock File System API +/////////////////////////////// + +/** + * Resets the mock file system to empty state. + * Call this in setUp() before each test. + */ +void mock_fs_reset(void); + +/** + * Adds a file to the mock file system with given content. + * + * @param path File path (e.g., "/mnt/SDCARD/test.txt") + * @param content File content as string (can include newlines) + * + * Example: + * mock_fs_add_file("/data/map.txt", "mario.gb\tSuper Mario\nzelda.gb\tZelda\n"); + */ +void mock_fs_add_file(const char* path, const char* content); + +/** + * Checks if a file exists in the mock file system. + * + * @param path File path to check + * @return 1 if file exists, 0 otherwise + */ +int mock_fs_exists(const char* path); + +/////////////////////////////// +// Wrapped Functions +// These are provided by fs_mocks.c and intercept real libc calls via --wrap +/////////////////////////////// + +// When compiling with -Wl,--wrap=exists, calls to exists() go to __wrap_exists() +// __wrap_exists can call __real_exists() to get the real implementation +int __wrap_exists(char* path); + +// When compiling with -Wl,--wrap=fopen +FILE* __wrap_fopen(const char* pathname, const char* mode); + +// When compiling with -Wl,--wrap=fclose +int __wrap_fclose(FILE* stream); + +// When compiling with -Wl,--wrap=fgets +char* __wrap_fgets(char* s, int size, FILE* stream); + +#endif // FS_MOCKS_H diff --git a/tests/support/platform.h b/tests/support/platform.h index 81bda00b..2afc5b0b 100644 --- a/tests/support/platform.h +++ b/tests/support/platform.h @@ -30,4 +30,11 @@ #define CODE_MENU CODE_NA #define JOY_MENU JOY_NA +// Modifier button definitions (required by pad.c) +// Set these to BTN_NONE to indicate no modifier button configuration +#define BTN_MOD_VOLUME BTN_NONE +#define BTN_MOD_BRIGHTNESS BTN_NONE +#define BTN_MOD_PLUS BTN_NONE +#define BTN_MOD_MINUS BTN_NONE + #endif diff --git a/tests/support/platform_mocks.c b/tests/support/platform_mocks.c new file mode 100644 index 00000000..b3e6ffe0 --- /dev/null +++ b/tests/support/platform_mocks.c @@ -0,0 +1,110 @@ +/** + * platform_mocks.c - Mock platform function implementations + * + * Provides controllable mock implementations of PLAT_* functions for testing. + * These override the real platform implementations via link-time substitution. + */ + +#include "platform_mocks.h" +#include "sdl_fakes.h" +#include + +/////////////////////////////// +// Mock State +/////////////////////////////// + +static struct { + // Battery state + int battery_charge; + int is_charging; + + // Event queue + SDL_Event queued_events[10]; + int event_count; + int event_index; + + // Video state + int video_width; + int video_height; +} mock_state = { + .battery_charge = 100, + .is_charging = 0, + .event_count = 0, + .event_index = 0, + .video_width = 640, + .video_height = 480, +}; + +/////////////////////////////// +// Mock Control Functions +/////////////////////////////// + +void mock_set_battery(int charge, int charging) { + mock_state.battery_charge = charge; + mock_state.is_charging = charging; +} + +void mock_queue_event(SDL_Event* event) { + if (mock_state.event_count < 10) { + memcpy(&mock_state.queued_events[mock_state.event_count], event, sizeof(SDL_Event)); + mock_state.event_count++; + } +} + +void mock_reset_all(void) { + mock_state.battery_charge = 100; + mock_state.is_charging = 0; + mock_state.event_count = 0; + mock_state.event_index = 0; + mock_state.video_width = 640; + mock_state.video_height = 480; + + // Reset fff fakes + RESET_FAKE(SDL_PollEvent); + FFF_RESET_HISTORY(); +} + +/////////////////////////////// +// PLAT_* Mock Implementations +// (These override real platform functions at link time) +/////////////////////////////// + +void PLAT_initInput(void) { + // No-op for tests +} + +void PLAT_quitInput(void) { + // No-op for tests +} + +void PLAT_pollInput(void) { + // Process queued events by calling the SDL_PollEvent fake + // The fake's behavior is controlled by tests + if (mock_state.event_index < mock_state.event_count) { + SDL_Event* event = &mock_state.queued_events[mock_state.event_index]; + mock_state.event_index++; + + // Configure SDL_PollEvent to return this event + SDL_PollEvent_fake.return_val = 1; // Has event + + // The actual SDL_PollEvent fake will copy event data + // This is handled in the test setup + } else { + SDL_PollEvent_fake.return_val = 0; // No more events + } +} + +void PLAT_getBatteryStatus(int* is_charging, int* charge) { + *is_charging = mock_state.is_charging; + *charge = mock_state.battery_charge; +} + +// Minimal no-op implementations for other PLAT functions +void PLAT_initVideo(void) {} +void PLAT_quitVideo(void) {} +void PLAT_clearVideo(SDL_Surface* screen) {} +void PLAT_flip(SDL_Surface* screen, int sync) {} +void PLAT_vsync(int remaining) {} +void PLAT_setCPUSpeed(int speed) {} +void PLAT_setRumble(int strength) {} +void PLAT_enableBacklight(int enable) {} diff --git a/tests/support/platform_mocks.h b/tests/support/platform_mocks.h new file mode 100644 index 00000000..56e75bf3 --- /dev/null +++ b/tests/support/platform_mocks.h @@ -0,0 +1,66 @@ +/** + * platform_mocks.h - Mock platform functions for testing + * + * Provides mock implementations of PLAT_* functions that can be controlled + * from tests. Uses link-time substitution to replace real platform code. + * + * Mock State Control Functions: + * - mock_set_battery() - Set battery charge and charging state + * - mock_set_video_size() - Configure mock video surface dimensions + * - mock_queue_event() - Queue an SDL event for next pollInput call + * - mock_reset_all() - Reset all mock state to defaults + * + * Usage: + * mock_set_battery(75, 1); // 75% charge, charging + * mock_queue_event(&event); // Queue keyboard event + * PLAT_pollInput(); // Will process queued event + */ + +#ifndef PLATFORM_MOCKS_H +#define PLATFORM_MOCKS_H + +#include "sdl_stubs.h" + +/////////////////////////////// +// Mock State Control +/////////////////////////////// + +/** + * Sets the mock battery state returned by PLAT_getBatteryStatus() + * + * @param charge Battery charge percentage (0-100) + * @param charging 1 if charging, 0 if not charging + */ +void mock_set_battery(int charge, int charging); + +/** + * Queues an SDL event to be returned by the next PLAT_pollInput() call + * + * @param event Event to queue (will be copied internally) + */ +void mock_queue_event(SDL_Event* event); + +/** + * Resets all mock state to default values + * + * Default state: + * - Battery: 100%, not charging + * - No queued events + * - Video: 640x480 + */ +void mock_reset_all(void); + +/////////////////////////////// +// Mock PLAT_* implementations +// (Defined in platform_mocks.c, replace real platform code via linking) +/////////////////////////////// + +// These are declared here for documentation but implemented in platform_mocks.c +// They override the real PLAT_* functions at link time. + +// void PLAT_initInput(void); +// void PLAT_quitInput(void); +// void PLAT_pollInput(void); +// void PLAT_getBatteryStatus(int* is_charging, int* charge); + +#endif // PLATFORM_MOCKS_H diff --git a/tests/support/sdl_fakes.c b/tests/support/sdl_fakes.c new file mode 100644 index 00000000..6ed0e4b6 --- /dev/null +++ b/tests/support/sdl_fakes.c @@ -0,0 +1,35 @@ +/** + * sdl_fakes.c - SDL function fake implementations + * + * Defines the actual fake function instances using fff macros. + * This file must be included/compiled exactly once in the test suite. + */ + +#include "sdl_fakes.h" + +// Define fff globals (must be done once per test executable) +DEFINE_FFF_GLOBALS; + +/////////////////////////////// +// Event System Fake Definitions +/////////////////////////////// + +DEFINE_FAKE_VALUE_FUNC(int, SDL_PollEvent, SDL_Event*); + +/////////////////////////////// +// TTF Fake Definitions - Phase 4 +/////////////////////////////// + +DEFINE_FAKE_VALUE_FUNC(int, TTF_SizeUTF8, TTF_Font*, const char*, int*, int*); + +/////////////////////////////// +// Surface Management Fake Definitions (future) +/////////////////////////////// + +// Will be added later if needed + +/////////////////////////////// +// Audio Fake Definitions (future) +/////////////////////////////// + +// Will be added in Phase 5 diff --git a/tests/support/sdl_fakes.h b/tests/support/sdl_fakes.h new file mode 100644 index 00000000..2bd4fc61 --- /dev/null +++ b/tests/support/sdl_fakes.h @@ -0,0 +1,67 @@ +/** + * sdl_fakes.h - SDL function fakes using fff framework + * + * Declares fake SDL functions for unit testing. Uses the Fake Function + * Framework (fff) to create mockable versions of SDL functions. + * + * Functions are added incrementally as needed for different test suites: + * - PAD testing: Event polling + * - GFX testing: Surface management, blitting + * - SND testing: Audio management + * + * Usage: + * #include "sdl_fakes.h" + * RESET_FAKE(SDL_PollEvent); // Reset before each test + * SDL_PollEvent_fake.return_val = 1; // Configure mock behavior + */ + +#ifndef SDL_FAKES_H +#define SDL_FAKES_H + +#include "fff/fff.h" +#include "sdl_stubs.h" + +/////////////////////////////// +// Event System Fakes +/////////////////////////////// + +/** + * Fake for SDL_PollEvent - Used by PAD input system + * + * Usage: + * SDL_Event mock_event = {.type = SDL_KEYDOWN}; + * SDL_PollEvent_fake.return_val = 1; // Has event + * // On next call, copy mock_event to the output parameter + */ +DECLARE_FAKE_VALUE_FUNC(int, SDL_PollEvent, SDL_Event*); + +/////////////////////////////// +// TTF (TrueType Font) Fakes - Phase 4 GFX Testing +/////////////////////////////// + +/** + * Fake for TTF_SizeUTF8 - Returns the size of rendered text + * + * Usage: + * TTF_SizeUTF8_fake.custom_fake = my_ttf_size_mock; + * // Custom function can calculate width based on string length + */ +DECLARE_FAKE_VALUE_FUNC(int, TTF_SizeUTF8, TTF_Font*, const char*, int*, int*); + +/////////////////////////////// +// Surface Management Fakes (for future GFX testing) +/////////////////////////////// + +// Will be added later if needed: +// DECLARE_FAKE_VALUE_FUNC(SDL_Surface*, SDL_CreateRGBSurface, ...); +// DECLARE_FAKE_VOID_FUNC(SDL_FreeSurface, SDL_Surface*); + +/////////////////////////////// +// Audio Fakes (for future SND testing) +/////////////////////////////// + +// Will be added in Phase 5: +// DECLARE_FAKE_VOID_FUNC(SDL_LockAudio); +// DECLARE_FAKE_VOID_FUNC(SDL_UnlockAudio); + +#endif // SDL_FAKES_H diff --git a/tests/support/sdl_stubs.h b/tests/support/sdl_stubs.h new file mode 100644 index 00000000..c8519cd6 --- /dev/null +++ b/tests/support/sdl_stubs.h @@ -0,0 +1,134 @@ +/** + * sdl_stubs.h - Minimal SDL type definitions for testing + * + * Provides stub SDL types needed for unit testing MinUI's api.c functions. + * Only includes the fields actually used in tests (minimal approach). + * + * This is NOT a complete SDL implementation - just enough type structure + * to allow compilation and testing of platform-independent logic. + * + * Do NOT include real SDL headers when using these stubs. + */ + +#ifndef SDL_STUBS_H +#define SDL_STUBS_H + +#include + +/////////////////////////////// +// Basic SDL typedefs +/////////////////////////////// + +typedef uint8_t Uint8; +typedef uint16_t Uint16; +typedef uint32_t Uint32; +typedef int16_t Sint16; +typedef int32_t Sint32; + +/////////////////////////////// +// SDL_PixelFormat +/////////////////////////////// + +typedef struct SDL_PixelFormat { + Uint8 BitsPerPixel; + Uint8 BytesPerPixel; + Uint32 Rmask; + Uint32 Gmask; + Uint32 Bmask; + Uint32 Amask; +} SDL_PixelFormat; + +/////////////////////////////// +// SDL_Surface +/////////////////////////////// + +typedef struct SDL_Surface { + Uint32 flags; + SDL_PixelFormat* format; + int w; + int h; + int pitch; + void* pixels; + void* userdata; + int locked; + void* lock_data; + // Clipping rectangle + struct { + int x, y, w, h; + } clip_rect; + int refcount; +} SDL_Surface; + +/////////////////////////////// +// SDL_Rect +/////////////////////////////// + +typedef struct SDL_Rect { + int x, y; + int w, h; +} SDL_Rect; + +/////////////////////////////// +// SDL Event System +/////////////////////////////// + +typedef enum { + SDL_KEYDOWN = 0x300, + SDL_KEYUP = 0x301, + SDL_QUIT = 0x100, +} SDL_EventType; + +typedef enum { + SDLK_UNKNOWN = 0, + SDLK_a = 'a', + SDLK_b = 'b', + SDLK_ESCAPE = 27, + // Add more as needed +} SDL_Keycode; + +typedef enum { + SDL_SCANCODE_UNKNOWN = 0, + SDL_SCANCODE_A = 4, + SDL_SCANCODE_B = 5, + SDL_SCANCODE_ESCAPE = 41, + SDL_SCANCODE_SPACE = 44, + SDL_SCANCODE_LCTRL = 224, + SDL_SCANCODE_LSHIFT = 225, + SDL_SCANCODE_LALT = 226, + // Add more as needed +} SDL_Scancode; + +typedef struct SDL_Keysym { + SDL_Scancode scancode; + SDL_Keycode sym; + Uint16 mod; +} SDL_Keysym; + +typedef struct SDL_KeyboardEvent { + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + Uint8 state; + Uint8 repeat; + SDL_Keysym keysym; +} SDL_KeyboardEvent; + +typedef union SDL_Event { + Uint32 type; + SDL_KeyboardEvent key; +} SDL_Event; + +/////////////////////////////// +// SDL_ttf (TrueType Font) Types +/////////////////////////////// + +/** + * Opaque font structure + * In real SDL_ttf this is complex, but for testing we just need a placeholder + */ +typedef struct TTF_Font { + int point_size; + int style; +} TTF_Font; + +#endif // SDL_STUBS_H diff --git a/tests/unit/all/common/test_api_pad.c b/tests/unit/all/common/test_api_pad.c new file mode 100644 index 00000000..14276dd0 --- /dev/null +++ b/tests/unit/all/common/test_api_pad.c @@ -0,0 +1,385 @@ +/** + * test_api_pad.c - Unit tests for PAD (input) functions + * + * Tests the button state management and analog stick conversion logic. + * These functions are pure state manipulation using bitwise operations, + * making them ideal candidates for unit testing. + * + * Test coverage: + * - PAD_reset() - Clearing all button states + * - PAD_setAnalog() - Analog stick to digital button conversion + * - PAD_anyJustPressed/anyPressed/anyJustReleased() - Query functions + * - PAD_justPressed/isPressed/justReleased/justRepeated() - Button-specific queries + * - PAD_tappedMenu() - Menu tap detection with timing + */ + +#include "../../support/unity/unity.h" +#include "../../../../workspace/all/common/pad.h" +#include + +// Test helpers +static void resetPadState(void) { + pad.just_pressed = BTN_NONE; + pad.is_pressed = BTN_NONE; + pad.just_released = BTN_NONE; + pad.just_repeated = BTN_NONE; + memset(pad.repeat_at, 0, sizeof(pad.repeat_at)); +} + +void setUp(void) { + resetPadState(); +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// PAD_reset tests +/////////////////////////////// + +void test_PAD_reset_clears_all_button_states(void) { + // Set up some button state + pad.just_pressed = BTN_A | BTN_B; + pad.is_pressed = BTN_START | BTN_SELECT; + pad.just_released = BTN_X; + pad.just_repeated = BTN_DPAD_UP; + + // Reset should clear everything + PAD_reset(); + + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.just_pressed); + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.is_pressed); + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.just_released); + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.just_repeated); +} + +/////////////////////////////// +// PAD_setAnalog tests +/////////////////////////////// + +void test_PAD_setAnalog_positive_direction_from_neutral(void) { + // Simulate right analog stick moving right + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 20000, 1000); + + // Should set positive direction (right) + TEST_ASSERT_TRUE(pad.is_pressed & BTN_ANALOG_RIGHT); + TEST_ASSERT_TRUE(pad.just_pressed & BTN_ANALOG_RIGHT); + TEST_ASSERT_TRUE(pad.just_repeated & BTN_ANALOG_RIGHT); + TEST_ASSERT_EQUAL_UINT32(1000, pad.repeat_at[BTN_ID_ANALOG_RIGHT]); + + // Should NOT set negative direction (left) + TEST_ASSERT_FALSE(pad.is_pressed & BTN_ANALOG_LEFT); +} + +void test_PAD_setAnalog_negative_direction_from_neutral(void) { + // Simulate analog stick moving left + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, -20000, 2000); + + // Should set negative direction (left) + TEST_ASSERT_TRUE(pad.is_pressed & BTN_ANALOG_LEFT); + TEST_ASSERT_TRUE(pad.just_pressed & BTN_ANALOG_LEFT); + TEST_ASSERT_TRUE(pad.just_repeated & BTN_ANALOG_LEFT); + TEST_ASSERT_EQUAL_UINT32(2000, pad.repeat_at[BTN_ID_ANALOG_LEFT]); + + // Should NOT set positive direction (right) + TEST_ASSERT_FALSE(pad.is_pressed & BTN_ANALOG_RIGHT); +} + +void test_PAD_setAnalog_deadzone_prevents_activation(void) { + // Values within deadzone (< 0x4000 = 16384) should not activate + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 10000, 1000); + + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.is_pressed); + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.just_pressed); + + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, -10000, 1000); + + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.is_pressed); + TEST_ASSERT_EQUAL_INT(BTN_NONE, pad.just_pressed); +} + +void test_PAD_setAnalog_opposite_direction_cancels_previous(void) { + // First press right + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 20000, 1000); + TEST_ASSERT_TRUE(pad.is_pressed & BTN_ANALOG_RIGHT); + + // Clear transient state (simulating next frame) + pad.just_pressed = BTN_NONE; + pad.just_released = BTN_NONE; + pad.just_repeated = BTN_NONE; + + // Now press left - should cancel right + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, -20000, 2000); + + TEST_ASSERT_TRUE(pad.is_pressed & BTN_ANALOG_LEFT); + TEST_ASSERT_FALSE(pad.is_pressed & BTN_ANALOG_RIGHT); + TEST_ASSERT_TRUE(pad.just_released & BTN_ANALOG_RIGHT); +} + +void test_PAD_setAnalog_return_to_neutral_releases_both(void) { + // Press right + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 20000, 1000); + TEST_ASSERT_TRUE(pad.is_pressed & BTN_ANALOG_RIGHT); + + // Clear transient state + pad.just_pressed = BTN_NONE; + pad.just_released = BTN_NONE; + pad.just_repeated = BTN_NONE; + + // Return to neutral (within deadzone) + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 0, 2000); + + TEST_ASSERT_FALSE(pad.is_pressed & BTN_ANALOG_RIGHT); + TEST_ASSERT_FALSE(pad.is_pressed & BTN_ANALOG_LEFT); + TEST_ASSERT_TRUE(pad.just_released & BTN_ANALOG_RIGHT); +} + +void test_PAD_setAnalog_already_pressed_doesnt_trigger_just_pressed(void) { + // Press right + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 20000, 1000); + TEST_ASSERT_TRUE(pad.just_pressed & BTN_ANALOG_RIGHT); + + // Clear transient state + pad.just_pressed = BTN_NONE; + + // Still holding right - should not trigger just_pressed again + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 20000, 1100); + + TEST_ASSERT_TRUE(pad.is_pressed & BTN_ANALOG_RIGHT); + TEST_ASSERT_FALSE(pad.just_pressed & BTN_ANALOG_RIGHT); +} + +void test_PAD_setAnalog_release_clears_just_repeated(void) { + // Press right analog + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 20000, 1000); + TEST_ASSERT_TRUE(pad.just_repeated & BTN_ANALOG_RIGHT); + + // Clear transient state + pad.just_pressed = BTN_NONE; + pad.just_released = BTN_NONE; + pad.just_repeated = BTN_NONE; + + // Manually set just_repeated (simulating button held for repeat) + pad.just_repeated = BTN_ANALOG_RIGHT | BTN_A; + + // Return analog to neutral - should clear ONLY ANALOG_RIGHT from just_repeated + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 0, 2000); + + // just_repeated should have ANALOG_RIGHT cleared, but BTN_A should remain + TEST_ASSERT_FALSE(pad.just_repeated & BTN_ANALOG_RIGHT); + TEST_ASSERT_TRUE(pad.just_repeated & BTN_A); +} + +void test_PAD_setAnalog_release_with_multiple_buttons_repeated(void) { + // Setup: Multiple buttons are in just_repeated state + pad.is_pressed = BTN_ANALOG_LEFT | BTN_ANALOG_RIGHT; + pad.just_repeated = BTN_ANALOG_LEFT | BTN_ANALOG_RIGHT | BTN_B | BTN_SELECT; + + // Release right analog (return to neutral) + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, 0, 1000); + + // Verify: ONLY the released buttons cleared from just_repeated + TEST_ASSERT_FALSE(pad.just_repeated & BTN_ANALOG_RIGHT); + TEST_ASSERT_FALSE(pad.just_repeated & BTN_ANALOG_LEFT); + + // Other buttons in just_repeated should remain untouched + TEST_ASSERT_TRUE(pad.just_repeated & BTN_B); + TEST_ASSERT_TRUE(pad.just_repeated & BTN_SELECT); +} + +/////////////////////////////// +// PAD query function tests +/////////////////////////////// + +void test_PAD_anyJustPressed_returns_true_when_button_just_pressed(void) { + pad.just_pressed = BTN_A; + TEST_ASSERT_TRUE(PAD_anyJustPressed()); +} + +void test_PAD_anyJustPressed_returns_false_when_no_buttons_just_pressed(void) { + pad.just_pressed = BTN_NONE; + TEST_ASSERT_FALSE(PAD_anyJustPressed()); +} + +void test_PAD_anyPressed_returns_true_when_button_is_held(void) { + pad.is_pressed = BTN_START; + TEST_ASSERT_TRUE(PAD_anyPressed()); +} + +void test_PAD_anyPressed_returns_false_when_no_buttons_held(void) { + pad.is_pressed = BTN_NONE; + TEST_ASSERT_FALSE(PAD_anyPressed()); +} + +void test_PAD_anyJustReleased_returns_true_when_button_just_released(void) { + pad.just_released = BTN_B; + TEST_ASSERT_TRUE(PAD_anyJustReleased()); +} + +void test_PAD_anyJustReleased_returns_false_when_no_buttons_just_released(void) { + pad.just_released = BTN_NONE; + TEST_ASSERT_FALSE(PAD_anyJustReleased()); +} + +void test_PAD_justPressed_returns_true_for_specific_button(void) { + pad.just_pressed = BTN_A | BTN_B; + TEST_ASSERT_TRUE(PAD_justPressed(BTN_A)); + TEST_ASSERT_TRUE(PAD_justPressed(BTN_B)); + TEST_ASSERT_FALSE(PAD_justPressed(BTN_X)); +} + +void test_PAD_isPressed_returns_true_for_held_button(void) { + pad.is_pressed = BTN_START | BTN_SELECT; + TEST_ASSERT_TRUE(PAD_isPressed(BTN_START)); + TEST_ASSERT_TRUE(PAD_isPressed(BTN_SELECT)); + TEST_ASSERT_FALSE(PAD_isPressed(BTN_MENU)); +} + +void test_PAD_justReleased_returns_true_for_released_button(void) { + pad.just_released = BTN_L1 | BTN_R1; + TEST_ASSERT_TRUE(PAD_justReleased(BTN_L1)); + TEST_ASSERT_TRUE(PAD_justReleased(BTN_R1)); + TEST_ASSERT_FALSE(PAD_justReleased(BTN_L2)); +} + +void test_PAD_justRepeated_returns_true_for_repeating_button(void) { + pad.just_repeated = BTN_DPAD_UP; + TEST_ASSERT_TRUE(PAD_justRepeated(BTN_DPAD_UP)); + TEST_ASSERT_FALSE(PAD_justRepeated(BTN_DPAD_DOWN)); +} + +/////////////////////////////// +// PAD_tappedMenu tests +/////////////////////////////// + +void test_PAD_tappedMenu_detects_quick_menu_tap(void) { + uint32_t start_time = 1000; + + // Simulate menu button press + pad.just_pressed = BTN_MENU; + pad.is_pressed = BTN_MENU; + PAD_tappedMenu(start_time); + + // Clear just_pressed (simulating next frame) + pad.just_pressed = BTN_NONE; + + // Release menu button quickly (within 250ms) + uint32_t release_time = start_time + 200; + pad.is_pressed = BTN_NONE; + pad.just_released = BTN_MENU; + + TEST_ASSERT_TRUE(PAD_tappedMenu(release_time)); +} + +void test_PAD_tappedMenu_ignores_held_menu_button(void) { + uint32_t start_time = 1000; + + // Simulate menu button press + pad.just_pressed = BTN_MENU; + pad.is_pressed = BTN_MENU; + PAD_tappedMenu(start_time); + + // Clear just_pressed + pad.just_pressed = BTN_NONE; + + // Release menu button after too long (> 250ms) + uint32_t release_time = start_time + 300; + pad.is_pressed = BTN_NONE; + pad.just_released = BTN_MENU; + + TEST_ASSERT_FALSE(PAD_tappedMenu(release_time)); +} + +void test_PAD_tappedMenu_ignores_brightness_adjustment(void) { + // This test only applies on platforms where BTN_MOD_BRIGHTNESS == BTN_MENU + // Skip the test if BTN_MOD_BRIGHTNESS is BTN_NONE (test environment) + if (BTN_MOD_BRIGHTNESS == BTN_NONE) { + TEST_PASS(); + return; + } + + uint32_t start_time = 1000; + + // Simulate menu button press + pad.just_pressed = BTN_MENU; + pad.is_pressed = BTN_MENU; + PAD_tappedMenu(start_time); + + // Clear just_pressed + pad.just_pressed = BTN_NONE; + + // Now press PLUS while holding MENU (brightness adjustment) + // Note: This only applies if BTN_MOD_BRIGHTNESS == BTN_MENU + if (BTN_MOD_BRIGHTNESS == BTN_MENU) { + pad.just_pressed = BTN_MOD_PLUS; + PAD_tappedMenu(start_time + 100); + + // Clear just_pressed + pad.just_pressed = BTN_NONE; + + // Release menu quickly - should be ignored due to brightness adjustment + uint32_t release_time = start_time + 200; + pad.is_pressed = BTN_NONE; + pad.just_released = BTN_MENU; + + TEST_ASSERT_FALSE(PAD_tappedMenu(release_time)); + } +} + +void test_PAD_tappedMenu_returns_false_when_menu_still_held(void) { + uint32_t start_time = 1000; + + // Simulate menu button press + pad.just_pressed = BTN_MENU; + pad.is_pressed = BTN_MENU; + PAD_tappedMenu(start_time); + + // Clear just_pressed + pad.just_pressed = BTN_NONE; + pad.just_released = BTN_NONE; + + // Menu is still held (not released) + TEST_ASSERT_FALSE(PAD_tappedMenu(start_time + 100)); +} + +/////////////////////////////// +// Test runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // PAD_reset tests + RUN_TEST(test_PAD_reset_clears_all_button_states); + + // PAD_setAnalog tests + RUN_TEST(test_PAD_setAnalog_positive_direction_from_neutral); + RUN_TEST(test_PAD_setAnalog_negative_direction_from_neutral); + RUN_TEST(test_PAD_setAnalog_deadzone_prevents_activation); + RUN_TEST(test_PAD_setAnalog_opposite_direction_cancels_previous); + RUN_TEST(test_PAD_setAnalog_return_to_neutral_releases_both); + RUN_TEST(test_PAD_setAnalog_already_pressed_doesnt_trigger_just_pressed); + RUN_TEST(test_PAD_setAnalog_release_clears_just_repeated); + RUN_TEST(test_PAD_setAnalog_release_with_multiple_buttons_repeated); + + // PAD query function tests + RUN_TEST(test_PAD_anyJustPressed_returns_true_when_button_just_pressed); + RUN_TEST(test_PAD_anyJustPressed_returns_false_when_no_buttons_just_pressed); + RUN_TEST(test_PAD_anyPressed_returns_true_when_button_is_held); + RUN_TEST(test_PAD_anyPressed_returns_false_when_no_buttons_held); + RUN_TEST(test_PAD_anyJustReleased_returns_true_when_button_just_released); + RUN_TEST(test_PAD_anyJustReleased_returns_false_when_no_buttons_just_released); + RUN_TEST(test_PAD_justPressed_returns_true_for_specific_button); + RUN_TEST(test_PAD_isPressed_returns_true_for_held_button); + RUN_TEST(test_PAD_justReleased_returns_true_for_released_button); + RUN_TEST(test_PAD_justRepeated_returns_true_for_repeating_button); + + // PAD_tappedMenu tests + RUN_TEST(test_PAD_tappedMenu_detects_quick_menu_tap); + RUN_TEST(test_PAD_tappedMenu_ignores_held_menu_button); + RUN_TEST(test_PAD_tappedMenu_ignores_brightness_adjustment); + RUN_TEST(test_PAD_tappedMenu_returns_false_when_menu_still_held); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_audio_resampler.c b/tests/unit/all/common/test_audio_resampler.c new file mode 100644 index 00000000..08d806cf --- /dev/null +++ b/tests/unit/all/common/test_audio_resampler.c @@ -0,0 +1,489 @@ +/** + * test_audio_resampler.c - Unit tests for audio resampling algorithm + * + * Tests the Bresenham-like nearest-neighbor resampling extracted from api.c. + * This algorithm handles sample rate conversion for audio playback. + * + * Test coverage: + * - Initialization and reset + * - Upsampling (e.g., 44100 -> 48000) - frame duplication + * - Downsampling (e.g., 48000 -> 44100) - frame skipping + * - Equal rates (passthrough) + * - Extreme ratios + * - Buffer wrapping behavior + * - State accumulation over many frames + */ + +#include "../../../support/unity/unity.h" +#include "../../../../workspace/all/common/audio_resampler.h" + +#include + +// Test buffer +#define TEST_BUFFER_SIZE 1000 +static SND_Frame test_buffer_data[TEST_BUFFER_SIZE]; +static AudioRingBuffer test_buffer; + +void setUp(void) { + // Clear test buffer + memset(test_buffer_data, 0, sizeof(test_buffer_data)); + test_buffer.frames = test_buffer_data; + test_buffer.capacity = TEST_BUFFER_SIZE; + test_buffer.write_pos = 0; +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// Initialization Tests +/////////////////////////////// + +void test_AudioResampler_init_sets_rates(void) { + AudioResampler resampler; + + AudioResampler_init(&resampler, 44100, 48000); + + TEST_ASSERT_EQUAL_INT(44100, resampler.sample_rate_in); + TEST_ASSERT_EQUAL_INT(48000, resampler.sample_rate_out); + TEST_ASSERT_EQUAL_INT(0, resampler.diff); +} + +void test_AudioResampler_reset_clears_diff(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + // Accumulate some diff + resampler.diff = 12345; + + AudioResampler_reset(&resampler); + + TEST_ASSERT_EQUAL_INT(0, resampler.diff); + // Rates should be unchanged + TEST_ASSERT_EQUAL_INT(44100, resampler.sample_rate_in); + TEST_ASSERT_EQUAL_INT(48000, resampler.sample_rate_out); +} + +void test_AudioResampler_isNeeded_returns_true_for_different_rates(void) { + TEST_ASSERT_TRUE(AudioResampler_isNeeded(44100, 48000)); + TEST_ASSERT_TRUE(AudioResampler_isNeeded(48000, 44100)); + TEST_ASSERT_TRUE(AudioResampler_isNeeded(22050, 48000)); +} + +void test_AudioResampler_isNeeded_returns_false_for_same_rates(void) { + TEST_ASSERT_FALSE(AudioResampler_isNeeded(44100, 44100)); + TEST_ASSERT_FALSE(AudioResampler_isNeeded(48000, 48000)); +} + +/////////////////////////////// +// Upsampling Tests (44100 -> 48000) +/////////////////////////////// + +void test_upsample_first_frame_written_not_consumed(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + SND_Frame frame = {.left = 100, .right = 200}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + // First frame: diff starts at 0, which is < 48000 + // So write frame, diff becomes 44100 + // 44100 < 48000, so consumed = 0 + TEST_ASSERT_EQUAL_INT(1, result.wrote_frame); + TEST_ASSERT_EQUAL_INT(0, result.consumed); + TEST_ASSERT_EQUAL_INT(44100, resampler.diff); + + // Verify frame was written to buffer + TEST_ASSERT_EQUAL_INT(100, test_buffer.frames[0].left); + TEST_ASSERT_EQUAL_INT(200, test_buffer.frames[0].right); + TEST_ASSERT_EQUAL_INT(1, test_buffer.write_pos); +} + +void test_upsample_second_frame_written_and_consumed(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + SND_Frame frame = {.left = 100, .right = 200}; + + // First call + AudioResampler_processFrame(&resampler, &test_buffer, frame); + + // Second call with same frame (since consumed=0 from first) + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + // diff starts at 44100, which is < 48000 + // Write frame, diff becomes 88200 + // 88200 >= 48000, so consumed = 1, diff = 40200 + TEST_ASSERT_EQUAL_INT(1, result.wrote_frame); + TEST_ASSERT_EQUAL_INT(1, result.consumed); + TEST_ASSERT_EQUAL_INT(40200, resampler.diff); + + // Verify both frames written + TEST_ASSERT_EQUAL_INT(2, test_buffer.write_pos); +} + +void test_upsample_pattern_over_many_frames(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + int writes = 0; + int consumes = 0; + int input_frame_id = 0; + + // Process 100 input frames + for (int i = 0; i < 200; i++) { // Up to 200 calls (may duplicate) + if (consumes >= 100) + break; // Processed all 100 input frames + + SND_Frame frame = {.left = (int16_t)input_frame_id, .right = (int16_t)input_frame_id}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (result.wrote_frame) + writes++; + if (result.consumed) + consumes++; + + // Advance input only if consumed + if (result.consumed) + input_frame_id++; + } + + // For 44100 -> 48000, ratio is 48000/44100 ≈ 1.0884 + // So 100 input frames should produce ~109 output frames + TEST_ASSERT_INT_WITHIN(2, 109, writes); + TEST_ASSERT_EQUAL_INT(100, consumes); +} + +/////////////////////////////// +// Downsampling Tests (48000 -> 44100) +/////////////////////////////// + +void test_downsample_first_frame_written_and_consumed(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 48000, 44100); + + SND_Frame frame = {.left = 100, .right = 200}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + // diff starts at 0, which is < 44100 + // Write frame, diff becomes 48000 + // 48000 >= 44100, so consumed = 1, diff = 3900 + TEST_ASSERT_EQUAL_INT(1, result.wrote_frame); + TEST_ASSERT_EQUAL_INT(1, result.consumed); + TEST_ASSERT_EQUAL_INT(3900, resampler.diff); +} + +void test_downsample_pattern_over_many_frames(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 48000, 44100); + + int writes = 0; + int consumes = 0; + + // Process 100 input frames + for (int i = 0; i < 100; i++) { + SND_Frame frame = {.left = (int16_t)i, .right = (int16_t)i}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (result.wrote_frame) + writes++; + if (result.consumed) + consumes++; + } + + // For 48000 -> 44100, ratio is 44100/48000 ≈ 0.91875 + // So 100 input frames should produce ~92 output frames + TEST_ASSERT_INT_WITHIN(2, 92, writes); + TEST_ASSERT_EQUAL_INT(100, consumes); +} + +void test_downsample_extreme_skips_frames(void) { + // Extreme downsample: 96000 -> 22050 (ratio ~0.23) + AudioResampler resampler; + AudioResampler_init(&resampler, 96000, 22050); + + int writes = 0; + int consumes = 0; + + // Process 100 input frames + for (int i = 0; i < 100; i++) { + SND_Frame frame = {.left = (int16_t)i, .right = (int16_t)i}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (result.wrote_frame) + writes++; + if (result.consumed) + consumes++; + } + + // Should skip most frames (~77%) + TEST_ASSERT_INT_WITHIN(5, 23, writes); + TEST_ASSERT_EQUAL_INT(100, consumes); +} + +/////////////////////////////// +// Equal Rate Tests (Passthrough) +/////////////////////////////// + +void test_equal_rates_writes_and_consumes_every_frame(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 48000, 48000); + + int writes = 0; + int consumes = 0; + + // Process 50 frames + for (int i = 0; i < 50; i++) { + SND_Frame frame = {.left = (int16_t)i, .right = (int16_t)i}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (result.wrote_frame) + writes++; + if (result.consumed) + consumes++; + } + + // Equal rates: every frame written and consumed (1:1) + TEST_ASSERT_EQUAL_INT(50, writes); + TEST_ASSERT_EQUAL_INT(50, consumes); +} + +/////////////////////////////// +// Buffer Wrapping Tests +/////////////////////////////// + +void test_buffer_wraps_at_capacity(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 44100); // No resampling for simplicity + + // Create small buffer + SND_Frame small_buffer[5]; + AudioRingBuffer buffer = {.frames = small_buffer, .capacity = 5, .write_pos = 0}; + + // Write 10 frames (should wrap twice) + for (int i = 0; i < 10; i++) { + SND_Frame frame = {.left = (int16_t)i, .right = (int16_t)i}; + AudioResampler_processFrame(&resampler, &buffer, frame); + } + + // Write position should have wrapped + TEST_ASSERT_EQUAL_INT(0, buffer.write_pos); // Wrapped back to 0 + + // Last write should be frame 9 at position 4 (then wrapped to 0) + TEST_ASSERT_EQUAL_INT(9, small_buffer[4].left); +} + +/////////////////////////////// +// State Accumulation Tests +/////////////////////////////// + +void test_resampler_accumulates_diff_correctly(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + SND_Frame frame = {0, 0}; + + // Call 1: diff = 0 -> write, diff = 44100, consumed = 0 + AudioResampler_processFrame(&resampler, &test_buffer, frame); + TEST_ASSERT_EQUAL_INT(44100, resampler.diff); + + // Call 2: diff = 44100 -> write, diff = 88200, consumed = 1, diff = 40200 + AudioResampler_processFrame(&resampler, &test_buffer, frame); + TEST_ASSERT_EQUAL_INT(40200, resampler.diff); + + // Call 3: diff = 40200 -> write, diff = 84300, consumed = 1, diff = 36300 + AudioResampler_processFrame(&resampler, &test_buffer, frame); + TEST_ASSERT_EQUAL_INT(36300, resampler.diff); +} + +void test_downsample_eventually_skips_write(void) { + // Use ratio where diff will exceed rate_out quickly + AudioResampler resampler; + AudioResampler_init(&resampler, 96000, 44100); + + SND_Frame frame = {100, 200}; + + // Process frames until we see a skip + int found_skip = 0; + for (int i = 0; i < 20; i++) { + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (!result.wrote_frame && result.consumed) { + found_skip = 1; + break; + } + } + + TEST_ASSERT_TRUE(found_skip); +} + +/////////////////////////////// +// Edge Cases +/////////////////////////////// + +void test_upsample_extreme_ratio(void) { + // 22050 -> 48000 (ratio ~2.18) + AudioResampler resampler; + AudioResampler_init(&resampler, 22050, 48000); + + int writes = 0; + int consumes = 0; + + for (int i = 0; i < 100; i++) { + SND_Frame frame = {.left = (int16_t)i, .right = (int16_t)i}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (result.wrote_frame) + writes++; + if (result.consumed) + consumes++; + + if (consumes >= 100) + break; + } + + // Should write more than 2x the input (~218 frames from 100 input) + TEST_ASSERT_GREATER_THAN(200, writes); + TEST_ASSERT_EQUAL_INT(100, consumes); +} + +void test_reset_clears_accumulated_state(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + // Accumulate state + SND_Frame frame = {0, 0}; + AudioResampler_processFrame(&resampler, &test_buffer, frame); + AudioResampler_processFrame(&resampler, &test_buffer, frame); + + TEST_ASSERT_NOT_EQUAL(0, resampler.diff); + + // Reset + AudioResampler_reset(&resampler); + + // Should behave like first frame again + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + TEST_ASSERT_EQUAL_INT(1, result.wrote_frame); + TEST_ASSERT_EQUAL_INT(0, result.consumed); + TEST_ASSERT_EQUAL_INT(44100, resampler.diff); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_upsample_44100_to_48000_realistic_scenario(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + // Simulate 1 second of audio (44100 input frames) + int writes = 0; + int consumes = 0; + int input_pos = 0; + + while (consumes < 44100) { + SND_Frame frame = {.left = (int16_t)input_pos, .right = (int16_t)input_pos}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (result.wrote_frame) + writes++; + if (result.consumed) + input_pos++; + + consumes += result.consumed; + + // Safety limit + if (writes > 50000) + break; + } + + // Should produce close to 48000 output frames + TEST_ASSERT_INT_WITHIN(10, 48000, writes); + TEST_ASSERT_EQUAL_INT(44100, consumes); +} + +void test_downsample_48000_to_44100_realistic_scenario(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 48000, 44100); + + // Simulate 1 second of audio (48000 input frames) + int writes = 0; + int consumes = 0; + + for (int i = 0; i < 48000; i++) { + SND_Frame frame = {.left = (int16_t)i, .right = (int16_t)i}; + ResampleResult result = AudioResampler_processFrame(&resampler, &test_buffer, frame); + + if (result.wrote_frame) + writes++; + if (result.consumed) + consumes++; + } + + // Should produce close to 44100 output frames + TEST_ASSERT_INT_WITHIN(10, 44100, writes); + TEST_ASSERT_EQUAL_INT(48000, consumes); +} + +void test_buffer_contains_correct_frame_data(void) { + AudioResampler resampler; + AudioResampler_init(&resampler, 44100, 48000); + + // Write a few distinct frames + SND_Frame f1 = {.left = 111, .right = 222}; + SND_Frame f2 = {.left = 333, .right = 444}; + + AudioResampler_processFrame(&resampler, &test_buffer, f1); // Write f1 + AudioResampler_processFrame(&resampler, &test_buffer, f1); // Write f1 again (duplicate) + AudioResampler_processFrame(&resampler, &test_buffer, f2); // Write f2 + + // Check buffer contents + TEST_ASSERT_EQUAL_INT(111, test_buffer.frames[0].left); + TEST_ASSERT_EQUAL_INT(222, test_buffer.frames[0].right); + TEST_ASSERT_EQUAL_INT(111, test_buffer.frames[1].left); // Duplicated + TEST_ASSERT_EQUAL_INT(333, test_buffer.frames[2].left); + TEST_ASSERT_EQUAL_INT(444, test_buffer.frames[2].right); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // Initialization + RUN_TEST(test_AudioResampler_init_sets_rates); + RUN_TEST(test_AudioResampler_reset_clears_diff); + RUN_TEST(test_AudioResampler_isNeeded_returns_true_for_different_rates); + RUN_TEST(test_AudioResampler_isNeeded_returns_false_for_same_rates); + + // Upsampling + RUN_TEST(test_upsample_first_frame_written_not_consumed); + RUN_TEST(test_upsample_second_frame_written_and_consumed); + RUN_TEST(test_upsample_pattern_over_many_frames); + + // Downsampling + RUN_TEST(test_downsample_first_frame_written_and_consumed); + RUN_TEST(test_downsample_pattern_over_many_frames); + RUN_TEST(test_downsample_extreme_skips_frames); + + // Equal rates + RUN_TEST(test_equal_rates_writes_and_consumes_every_frame); + + // Buffer wrapping + RUN_TEST(test_buffer_wraps_at_capacity); + + // State accumulation + RUN_TEST(test_resampler_accumulates_diff_correctly); + RUN_TEST(test_downsample_eventually_skips_write); + RUN_TEST(test_reset_clears_accumulated_state); + + // Integration + RUN_TEST(test_upsample_44100_to_48000_realistic_scenario); + RUN_TEST(test_downsample_48000_to_44100_realistic_scenario); + RUN_TEST(test_buffer_contains_correct_frame_data); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_binary_file_utils.c b/tests/unit/all/common/test_binary_file_utils.c new file mode 100644 index 00000000..aa2f0f55 --- /dev/null +++ b/tests/unit/all/common/test_binary_file_utils.c @@ -0,0 +1,270 @@ +/** + * test_binary_file_utils.c - Tests for binary file I/O operations + * + * Tests fread/fwrite operations using real temp files. + * Demonstrates binary data handling patterns used in minarch.c (SRAM, RTC, save states). + */ + +#include "../../../support/unity/unity.h" +#include "../../../../workspace/all/common/binary_file_utils.h" + +#include +#include +#include +#include + +void setUp(void) { + // Nothing to set up +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// Basic Binary I/O Tests +/////////////////////////////// + +void test_BinaryFile_write_read_small_buffer(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + // Write small binary buffer + uint8_t write_data[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + size_t written = BinaryFile_write(temp_path, write_data, 5); + TEST_ASSERT_EQUAL_UINT(5, written); + + // Read back + uint8_t read_data[5] = {0}; + size_t read_bytes = BinaryFile_read(temp_path, read_data, 5); + TEST_ASSERT_EQUAL_UINT(5, read_bytes); + + // Verify data matches + TEST_ASSERT_EQUAL_MEMORY(write_data, read_data, 5); + + unlink(temp_path); +} + +void test_BinaryFile_write_read_large_buffer(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + // Write 1KB buffer + uint8_t write_data[1024]; + for (int i = 0; i < 1024; i++) { + write_data[i] = (uint8_t)(i % 256); + } + + size_t written = BinaryFile_write(temp_path, write_data, 1024); + TEST_ASSERT_EQUAL_UINT(1024, written); + + // Read back + uint8_t read_data[1024] = {0}; + size_t read_bytes = BinaryFile_read(temp_path, read_data, 1024); + TEST_ASSERT_EQUAL_UINT(1024, read_bytes); + + // Verify + TEST_ASSERT_EQUAL_MEMORY(write_data, read_data, 1024); + + unlink(temp_path); +} + +void test_BinaryFile_write_read_sram_like_data(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + // Simulate SRAM data (32KB like Game Boy saves) + const size_t sram_size = 32 * 1024; + uint8_t* sram = malloc(sram_size); + TEST_ASSERT_NOT_NULL(sram); + + // Fill with pattern + for (size_t i = 0; i < sram_size; i++) { + sram[i] = (uint8_t)((i * 7) % 256); + } + + // Write + size_t written = BinaryFile_write(temp_path, sram, sram_size); + TEST_ASSERT_EQUAL_UINT(sram_size, written); + + // Read back + uint8_t* sram_read = malloc(sram_size); + TEST_ASSERT_NOT_NULL(sram_read); + memset(sram_read, 0, sram_size); + + size_t read_bytes = BinaryFile_read(temp_path, sram_read, sram_size); + TEST_ASSERT_EQUAL_UINT(sram_size, read_bytes); + + // Verify + TEST_ASSERT_EQUAL_MEMORY(sram, sram_read, sram_size); + + free(sram); + free(sram_read); + unlink(temp_path); +} + +void test_BinaryFile_write_read_rtc_like_data(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + // Simulate RTC data (small, like Game Boy RTC - few bytes) + uint8_t rtc_data[] = { + 0x12, 0x34, 0x56, 0x78, // Seconds, minutes, hours, days + 0xAB, 0xCD, 0xEF, 0x01 // Day carry, halt flag, etc. + }; + + size_t written = BinaryFile_write(temp_path, rtc_data, sizeof(rtc_data)); + TEST_ASSERT_EQUAL_UINT(sizeof(rtc_data), written); + + uint8_t rtc_read[sizeof(rtc_data)] = {0}; + size_t read_bytes = BinaryFile_read(temp_path, rtc_read, sizeof(rtc_read)); + TEST_ASSERT_EQUAL_UINT(sizeof(rtc_data), read_bytes); + + TEST_ASSERT_EQUAL_MEMORY(rtc_data, rtc_read, sizeof(rtc_data)); + + unlink(temp_path); +} + +/////////////////////////////// +// Error Cases +/////////////////////////////// + +void test_BinaryFile_read_nonexistent_file(void) { + uint8_t buffer[100]; + size_t read_bytes = BinaryFile_read("/nonexistent/file.bin", buffer, 100); + TEST_ASSERT_EQUAL_UINT(0, read_bytes); +} + +void test_BinaryFile_write_invalid_path(void) { + uint8_t data[] = {0x01, 0x02, 0x03}; + size_t written = BinaryFile_write("/nonexistent/dir/file.bin", data, 3); + TEST_ASSERT_EQUAL_UINT(0, written); +} + +void test_BinaryFile_read_null_buffer(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + close(fd); + + size_t read_bytes = BinaryFile_read(temp_path, NULL, 100); + TEST_ASSERT_EQUAL_UINT(0, read_bytes); + + unlink(temp_path); +} + +void test_BinaryFile_write_null_buffer(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + close(fd); + + size_t written = BinaryFile_write(temp_path, NULL, 100); + TEST_ASSERT_EQUAL_UINT(0, written); + + unlink(temp_path); +} + +void test_BinaryFile_read_zero_size(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + close(fd); + + uint8_t buffer[100]; + size_t read_bytes = BinaryFile_read(temp_path, buffer, 0); + TEST_ASSERT_EQUAL_UINT(0, read_bytes); + + unlink(temp_path); +} + +void test_BinaryFile_write_zero_size(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + close(fd); + + uint8_t data[] = {0x01}; + size_t written = BinaryFile_write(temp_path, data, 0); + TEST_ASSERT_EQUAL_UINT(0, written); + + unlink(temp_path); +} + +/////////////////////////////// +// Special Cases +/////////////////////////////// + +void test_BinaryFile_write_overwrites_existing(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + close(fd); + + // Write initial data + uint8_t data1[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + BinaryFile_write(temp_path, data1, 5); + + // Overwrite with smaller data + uint8_t data2[] = {0xFF, 0xEE}; + size_t written = BinaryFile_write(temp_path, data2, 2); + TEST_ASSERT_EQUAL_UINT(2, written); + + // Read back - should only have 2 bytes now + uint8_t read_data[5] = {0}; + size_t read_bytes = BinaryFile_read(temp_path, read_data, 5); + TEST_ASSERT_EQUAL_UINT(2, read_bytes); + TEST_ASSERT_EQUAL_UINT8(0xFF, read_data[0]); + TEST_ASSERT_EQUAL_UINT8(0xEE, read_data[1]); + + unlink(temp_path); +} + +void test_BinaryFile_read_partial(void) { + char temp_path[] = "/tmp/bintest_XXXXXX"; + int fd = mkstemp(temp_path); + close(fd); + + // Write 10 bytes + uint8_t write_data[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + BinaryFile_write(temp_path, write_data, 10); + + // Read only first 5 bytes + uint8_t read_data[5] = {0}; + size_t read_bytes = BinaryFile_read(temp_path, read_data, 5); + TEST_ASSERT_EQUAL_UINT(5, read_bytes); + TEST_ASSERT_EQUAL_MEMORY(write_data, read_data, 5); + + unlink(temp_path); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // Basic I/O + RUN_TEST(test_BinaryFile_write_read_small_buffer); + RUN_TEST(test_BinaryFile_write_read_large_buffer); + RUN_TEST(test_BinaryFile_write_read_sram_like_data); + RUN_TEST(test_BinaryFile_write_read_rtc_like_data); + + // Error cases + RUN_TEST(test_BinaryFile_read_nonexistent_file); + RUN_TEST(test_BinaryFile_write_invalid_path); + RUN_TEST(test_BinaryFile_read_null_buffer); + RUN_TEST(test_BinaryFile_write_null_buffer); + RUN_TEST(test_BinaryFile_read_zero_size); + RUN_TEST(test_BinaryFile_write_zero_size); + + // Special cases + RUN_TEST(test_BinaryFile_write_overwrites_existing); + RUN_TEST(test_BinaryFile_read_partial); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_collection_parser.c b/tests/unit/all/common/test_collection_parser.c new file mode 100644 index 00000000..7e76cc94 --- /dev/null +++ b/tests/unit/all/common/test_collection_parser.c @@ -0,0 +1,267 @@ +/** + * test_collection_parser.c - Unit tests for collection file parser + * + * Tests collection .txt parsing logic extracted from minui.c. + * Collections are custom ROM lists (one path per line). + * + * Test coverage: + * - Basic parsing (single entry, multiple entries) + * - Path validation (only existing ROMs included) + * - Empty line handling + * - PAK vs ROM detection + * - Error handling (missing files, empty collections) + * + * Note: Uses GCC --wrap for file mocking (Docker-only) + */ + +#include "../../../support/unity/unity.h" +#include "../../../support/fs_mocks.h" +#include "../../../../workspace/all/common/collection_parser.h" + +#include + +void setUp(void) { + mock_fs_reset(); +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// Basic Parsing Tests +/////////////////////////////// + +void test_Collection_parse_single_rom(void) { + mock_fs_add_file("/Collections/Test.txt", "/Roms/GB/mario.gb\n"); + mock_fs_add_file("/mnt/SDCARD/Roms/GB/mario.gb", "rom"); + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Test.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(1, count); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/GB/mario.gb", entries[0]->path); + TEST_ASSERT_FALSE(entries[0]->is_pak); + + Collection_freeEntries(entries, count); +} + +void test_Collection_parse_multiple_roms(void) { + mock_fs_add_file("/Collections/Favorites.txt", + "/Roms/GB/mario.gb\n" + "/Roms/NES/zelda.nes\n" + "/Roms/SNES/metroid.smc\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/GB/mario.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/NES/zelda.nes", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/SNES/metroid.smc", "rom"); + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Favorites.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(3, count); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/GB/mario.gb", entries[0]->path); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/NES/zelda.nes", entries[1]->path); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/SNES/metroid.smc", entries[2]->path); + + Collection_freeEntries(entries, count); +} + +void test_Collection_parse_skips_missing_roms(void) { + mock_fs_add_file("/Collections/Test.txt", + "/Roms/exists.gb\n" + "/Roms/missing.gb\n" + "/Roms/also_exists.nes\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/exists.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/also_exists.nes", "rom"); + // Not creating missing.gb + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Test.txt", "/mnt/SDCARD", &count); + + // Should only include the 2 that exist + TEST_ASSERT_EQUAL_INT(2, count); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/exists.gb", entries[0]->path); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/also_exists.nes", entries[1]->path); + + Collection_freeEntries(entries, count); +} + +void test_Collection_parse_skips_empty_lines(void) { + mock_fs_add_file("/Collections/Test.txt", + "\n" + "/Roms/game1.gb\n" + "\n" + "\n" + "/Roms/game2.gb\n" + "\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/game1.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/game2.gb", "rom"); + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Test.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(2, count); + + Collection_freeEntries(entries, count); +} + +/////////////////////////////// +// PAK vs ROM Detection Tests +/////////////////////////////// + +void test_Collection_parse_detects_pak_files(void) { + mock_fs_add_file("/Collections/Apps.txt", "/Tools/say.pak\n"); + mock_fs_add_file("/mnt/SDCARD/Tools/say.pak", "pak"); + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Apps.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(1, count); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Tools/say.pak", entries[0]->path); + TEST_ASSERT_TRUE(entries[0]->is_pak); + + Collection_freeEntries(entries, count); +} + +void test_Collection_parse_mixed_roms_and_paks(void) { + mock_fs_add_file("/Collections/Mixed.txt", + "/Roms/GB/game.gb\n" + "/Tools/app.pak\n" + "/Roms/NES/game.nes\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/GB/game.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Tools/app.pak", "pak"); + mock_fs_add_file("/mnt/SDCARD/Roms/NES/game.nes", "rom"); + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Mixed.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(3, count); + TEST_ASSERT_FALSE(entries[0]->is_pak); // game.gb + TEST_ASSERT_TRUE(entries[1]->is_pak); // app.pak + TEST_ASSERT_FALSE(entries[2]->is_pak); // game.nes + + Collection_freeEntries(entries, count); +} + +/////////////////////////////// +// Error Cases +/////////////////////////////// + +void test_Collection_parse_file_not_found_returns_zero(void) { + int count; + Collection_Entry** entries = Collection_parse("/nonexistent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + Collection_freeEntries(entries, count); +} + +void test_Collection_parse_empty_file_returns_zero(void) { + mock_fs_add_file("/Collections/Empty.txt", ""); + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Empty.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + Collection_freeEntries(entries, count); +} + +void test_Collection_parse_all_roms_missing_returns_zero(void) { + mock_fs_add_file("/Collections/Test.txt", + "/Roms/missing1.gb\n" + "/Roms/missing2.nes\n"); + + // Don't create any ROM files + int count; + Collection_Entry** entries = Collection_parse("/Collections/Test.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + Collection_freeEntries(entries, count); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_Collection_parse_realistic_favorites(void) { + mock_fs_add_file("/mnt/SDCARD/Collections/Favorites.txt", + "/Roms/GB/Super Mario Land (World).gb\n" + "/Roms/NES/Super Mario Bros (World).nes\n" + "/Roms/SNES/Super Metroid (USA, Europe).smc\n" + "/Roms/GBA/Pokemon - Emerald (USA).gba\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/GB/Super Mario Land (World).gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/NES/Super Mario Bros (World).nes", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/SNES/Super Metroid (USA, Europe).smc", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/GBA/Pokemon - Emerald (USA).gba", "rom"); + + int count; + Collection_Entry** entries = + Collection_parse("/mnt/SDCARD/Collections/Favorites.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(4, count); + + // Verify paths + TEST_ASSERT_TRUE(strstr(entries[0]->path, "Super Mario Land") != NULL); + TEST_ASSERT_TRUE(strstr(entries[1]->path, "Super Mario Bros") != NULL); + TEST_ASSERT_TRUE(strstr(entries[2]->path, "Super Metroid") != NULL); + TEST_ASSERT_TRUE(strstr(entries[3]->path, "Pokemon") != NULL); + + Collection_freeEntries(entries, count); +} + +void test_Collection_parse_maintains_order(void) { + mock_fs_add_file("/Collections/Ordered.txt", + "/Roms/game3.rom\n" + "/Roms/game1.rom\n" + "/Roms/game2.rom\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/game1.rom", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/game2.rom", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/game3.rom", "rom"); + + int count; + Collection_Entry** entries = Collection_parse("/Collections/Ordered.txt", "/mnt/SDCARD", &count); + + // Order should match file order, not alphabetical + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/game3.rom", entries[0]->path); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/game1.rom", entries[1]->path); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/game2.rom", entries[2]->path); + + Collection_freeEntries(entries, count); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // Basic parsing + RUN_TEST(test_Collection_parse_single_rom); + RUN_TEST(test_Collection_parse_multiple_roms); + RUN_TEST(test_Collection_parse_skips_missing_roms); + RUN_TEST(test_Collection_parse_skips_empty_lines); + + // PAK detection + RUN_TEST(test_Collection_parse_detects_pak_files); + RUN_TEST(test_Collection_parse_mixed_roms_and_paks); + + // Error cases + RUN_TEST(test_Collection_parse_file_not_found_returns_zero); + RUN_TEST(test_Collection_parse_empty_file_returns_zero); + RUN_TEST(test_Collection_parse_all_roms_missing_returns_zero); + + // Integration + RUN_TEST(test_Collection_parse_realistic_favorites); + RUN_TEST(test_Collection_parse_maintains_order); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_collections.c b/tests/unit/all/common/test_collections.c new file mode 100644 index 00000000..f6bf5a80 --- /dev/null +++ b/tests/unit/all/common/test_collections.c @@ -0,0 +1,541 @@ +/** + * test_collections.c - Unit tests for Array and Hash data structures + * + * Tests the generic data structures extracted from minui.c. + * These are pure logic with no dependencies, making them ideal for unit testing. + * + * Test coverage: + * - Array_new/free - Array lifecycle + * - Array_push/pop - Stack operations + * - Array_unshift - Insert at beginning + * - Array_reverse - Reverse order + * - StringArray_indexOf - String search + * - StringArray_free - String cleanup + * - Hash_new/free - Hash lifecycle + * - Hash_set/get - Key-value storage/retrieval + */ + +#define _POSIX_C_SOURCE 200809L // Required for strdup() + +#include "../../support/unity/unity.h" +#include "../../../../workspace/all/common/collections.h" +#include + +void setUp(void) { + // Nothing to set up +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// Array basic lifecycle tests +/////////////////////////////// + +void test_Array_new_creates_empty_array(void) { + Array* arr = Array_new(); + + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_EQUAL_INT(0, arr->count); + TEST_ASSERT_EQUAL_INT(8, arr->capacity); + TEST_ASSERT_NOT_NULL(arr->items); + + Array_free(arr); +} + +void test_Array_free_cleans_up(void) { + Array* arr = Array_new(); + Array_free(arr); + // If this doesn't crash, it passed + TEST_PASS(); +} + +/////////////////////////////// +// Array_push tests +/////////////////////////////// + +void test_Array_push_single_item(void) { + Array* arr = Array_new(); + int value = 42; + + Array_push(arr, &value); + + TEST_ASSERT_EQUAL_INT(1, arr->count); + TEST_ASSERT_EQUAL_PTR(&value, arr->items[0]); + + Array_free(arr); +} + +void test_Array_push_multiple_items(void) { + Array* arr = Array_new(); + int values[] = {1, 2, 3, 4, 5}; + + for (int i = 0; i < 5; i++) { + Array_push(arr, &values[i]); + } + + TEST_ASSERT_EQUAL_INT(5, arr->count); + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL_PTR(&values[i], arr->items[i]); + } + + Array_free(arr); +} + +void test_Array_push_grows_capacity(void) { + Array* arr = Array_new(); + int values[20]; + + // Push beyond initial capacity (8) + for (int i = 0; i < 20; i++) { + values[i] = i; + Array_push(arr, &values[i]); + } + + TEST_ASSERT_EQUAL_INT(20, arr->count); + TEST_ASSERT_GREATER_OR_EQUAL(20, arr->capacity); + + // Verify all items are still accessible + for (int i = 0; i < 20; i++) { + TEST_ASSERT_EQUAL_PTR(&values[i], arr->items[i]); + } + + Array_free(arr); +} + +/////////////////////////////// +// Array_pop tests +/////////////////////////////// + +void test_Array_pop_returns_last_item(void) { + Array* arr = Array_new(); + int values[] = {1, 2, 3}; + + for (int i = 0; i < 3; i++) { + Array_push(arr, &values[i]); + } + + void* popped = Array_pop(arr); + + TEST_ASSERT_EQUAL_PTR(&values[2], popped); + TEST_ASSERT_EQUAL_INT(2, arr->count); + + Array_free(arr); +} + +void test_Array_pop_empty_array_returns_null(void) { + Array* arr = Array_new(); + + void* popped = Array_pop(arr); + + TEST_ASSERT_NULL(popped); + TEST_ASSERT_EQUAL_INT(0, arr->count); + + Array_free(arr); +} + +void test_Array_pop_all_items(void) { + Array* arr = Array_new(); + int values[] = {1, 2, 3}; + + for (int i = 0; i < 3; i++) { + Array_push(arr, &values[i]); + } + + // Pop all items in reverse order + TEST_ASSERT_EQUAL_PTR(&values[2], Array_pop(arr)); + TEST_ASSERT_EQUAL_PTR(&values[1], Array_pop(arr)); + TEST_ASSERT_EQUAL_PTR(&values[0], Array_pop(arr)); + TEST_ASSERT_NULL(Array_pop(arr)); + + Array_free(arr); +} + +/////////////////////////////// +// Array_unshift tests +/////////////////////////////// + +void test_Array_unshift_empty_array(void) { + Array* arr = Array_new(); + int value = 42; + + Array_unshift(arr, &value); + + TEST_ASSERT_EQUAL_INT(1, arr->count); + TEST_ASSERT_EQUAL_PTR(&value, arr->items[0]); + + Array_free(arr); +} + +void test_Array_unshift_shifts_existing_items(void) { + Array* arr = Array_new(); + int values[] = {1, 2, 3}; + + // Push initial items + Array_push(arr, &values[1]); + Array_push(arr, &values[2]); + + // Unshift new first item + Array_unshift(arr, &values[0]); + + TEST_ASSERT_EQUAL_INT(3, arr->count); + TEST_ASSERT_EQUAL_PTR(&values[0], arr->items[0]); + TEST_ASSERT_EQUAL_PTR(&values[1], arr->items[1]); + TEST_ASSERT_EQUAL_PTR(&values[2], arr->items[2]); + + Array_free(arr); +} + +void test_Array_unshift_multiple_times(void) { + Array* arr = Array_new(); + int values[] = {1, 2, 3, 4}; + + // Unshift in reverse order to get correct final order + for (int i = 3; i >= 0; i--) { + Array_unshift(arr, &values[i]); + } + + TEST_ASSERT_EQUAL_INT(4, arr->count); + for (int i = 0; i < 4; i++) { + TEST_ASSERT_EQUAL_PTR(&values[i], arr->items[i]); + } + + Array_free(arr); +} + +/////////////////////////////// +// Array_reverse tests +/////////////////////////////// + +void test_Array_reverse_empty_array(void) { + Array* arr = Array_new(); + + Array_reverse(arr); + + TEST_ASSERT_EQUAL_INT(0, arr->count); + + Array_free(arr); +} + +void test_Array_reverse_single_item(void) { + Array* arr = Array_new(); + int value = 42; + + Array_push(arr, &value); + Array_reverse(arr); + + TEST_ASSERT_EQUAL_INT(1, arr->count); + TEST_ASSERT_EQUAL_PTR(&value, arr->items[0]); + + Array_free(arr); +} + +void test_Array_reverse_even_count(void) { + Array* arr = Array_new(); + int values[] = {1, 2, 3, 4}; + + for (int i = 0; i < 4; i++) { + Array_push(arr, &values[i]); + } + + Array_reverse(arr); + + TEST_ASSERT_EQUAL_INT(4, arr->count); + TEST_ASSERT_EQUAL_PTR(&values[3], arr->items[0]); + TEST_ASSERT_EQUAL_PTR(&values[2], arr->items[1]); + TEST_ASSERT_EQUAL_PTR(&values[1], arr->items[2]); + TEST_ASSERT_EQUAL_PTR(&values[0], arr->items[3]); + + Array_free(arr); +} + +void test_Array_reverse_odd_count(void) { + Array* arr = Array_new(); + int values[] = {1, 2, 3}; + + for (int i = 0; i < 3; i++) { + Array_push(arr, &values[i]); + } + + Array_reverse(arr); + + TEST_ASSERT_EQUAL_INT(3, arr->count); + TEST_ASSERT_EQUAL_PTR(&values[2], arr->items[0]); + TEST_ASSERT_EQUAL_PTR(&values[1], arr->items[1]); + TEST_ASSERT_EQUAL_PTR(&values[0], arr->items[2]); + + Array_free(arr); +} + +/////////////////////////////// +// StringArray tests +/////////////////////////////// + +void test_StringArray_indexOf_finds_string(void) { + Array* arr = Array_new(); + char* strings[] = {"apple", "banana", "cherry"}; + + for (int i = 0; i < 3; i++) { + Array_push(arr, strings[i]); + } + + TEST_ASSERT_EQUAL_INT(0, StringArray_indexOf(arr, "apple")); + TEST_ASSERT_EQUAL_INT(1, StringArray_indexOf(arr, "banana")); + TEST_ASSERT_EQUAL_INT(2, StringArray_indexOf(arr, "cherry")); + + Array_free(arr); +} + +void test_StringArray_indexOf_not_found(void) { + Array* arr = Array_new(); + char* strings[] = {"apple", "banana"}; + + for (int i = 0; i < 2; i++) { + Array_push(arr, strings[i]); + } + + TEST_ASSERT_EQUAL_INT(-1, StringArray_indexOf(arr, "orange")); + + Array_free(arr); +} + +void test_StringArray_indexOf_empty_array(void) { + Array* arr = Array_new(); + + TEST_ASSERT_EQUAL_INT(-1, StringArray_indexOf(arr, "test")); + + Array_free(arr); +} + +void test_StringArray_free_frees_strings(void) { + Array* arr = Array_new(); + + // Add dynamically allocated strings + Array_push(arr, strdup("hello")); + Array_push(arr, strdup("world")); + + // StringArray_free should free both the array and the strings + StringArray_free(arr); + + // If this doesn't leak memory, it passed + TEST_PASS(); +} + +/////////////////////////////// +// Hash basic lifecycle tests +/////////////////////////////// + +void test_Hash_new_creates_empty_hash(void) { + Hash* hash = Hash_new(); + + TEST_ASSERT_NOT_NULL(hash); + TEST_ASSERT_NOT_NULL(hash->keys); + TEST_ASSERT_NOT_NULL(hash->values); + TEST_ASSERT_EQUAL_INT(0, hash->keys->count); + TEST_ASSERT_EQUAL_INT(0, hash->values->count); + + Hash_free(hash); +} + +void test_Hash_free_cleans_up(void) { + Hash* hash = Hash_new(); + Hash_free(hash); + // If this doesn't crash, it passed + TEST_PASS(); +} + +/////////////////////////////// +// Hash_set tests +/////////////////////////////// + +void test_Hash_set_single_entry(void) { + Hash* hash = Hash_new(); + + Hash_set(hash, "name", "MinUI"); + + TEST_ASSERT_EQUAL_INT(1, hash->keys->count); + TEST_ASSERT_EQUAL_INT(1, hash->values->count); + + Hash_free(hash); +} + +void test_Hash_set_multiple_entries(void) { + Hash* hash = Hash_new(); + + Hash_set(hash, "name", "MinUI"); + Hash_set(hash, "version", "2024"); + Hash_set(hash, "platform", "miyoomini"); + + TEST_ASSERT_EQUAL_INT(3, hash->keys->count); + TEST_ASSERT_EQUAL_INT(3, hash->values->count); + + Hash_free(hash); +} + +void test_Hash_set_allows_duplicate_keys(void) { + Hash* hash = Hash_new(); + + Hash_set(hash, "key", "value1"); + Hash_set(hash, "key", "value2"); + + // Hash_set does not check for duplicates + TEST_ASSERT_EQUAL_INT(2, hash->keys->count); + + Hash_free(hash); +} + +/////////////////////////////// +// Hash_get tests +/////////////////////////////// + +void test_Hash_get_retrieves_value(void) { + Hash* hash = Hash_new(); + + Hash_set(hash, "name", "MinUI"); + Hash_set(hash, "version", "2024"); + + char* name = Hash_get(hash, "name"); + char* version = Hash_get(hash, "version"); + + TEST_ASSERT_EQUAL_STRING("MinUI", name); + TEST_ASSERT_EQUAL_STRING("2024", version); + + Hash_free(hash); +} + +void test_Hash_get_not_found_returns_null(void) { + Hash* hash = Hash_new(); + + Hash_set(hash, "name", "MinUI"); + + char* missing = Hash_get(hash, "missing"); + + TEST_ASSERT_NULL(missing); + + Hash_free(hash); +} + +void test_Hash_get_empty_hash_returns_null(void) { + Hash* hash = Hash_new(); + + char* value = Hash_get(hash, "anything"); + + TEST_ASSERT_NULL(value); + + Hash_free(hash); +} + +void test_Hash_get_with_duplicate_keys_returns_first(void) { + Hash* hash = Hash_new(); + + Hash_set(hash, "key", "first"); + Hash_set(hash, "key", "second"); + + char* value = Hash_get(hash, "key"); + + // Returns the first matching key + TEST_ASSERT_EQUAL_STRING("first", value); + + Hash_free(hash); +} + +/////////////////////////////// +// Integration tests +/////////////////////////////// + +void test_Hash_integration_rom_alias_map(void) { + Hash* hash = Hash_new(); + + // Simulate loading a ROM alias map + Hash_set(hash, "Super Mario Land.gb", "Mario Land"); + Hash_set(hash, "The Legend of Zelda - Link's Awakening.gb", "Zelda LA"); + Hash_set(hash, "Pokemon Red Version.gb", "Pokemon Red"); + + // Retrieve aliases + TEST_ASSERT_EQUAL_STRING("Mario Land", Hash_get(hash, "Super Mario Land.gb")); + TEST_ASSERT_EQUAL_STRING("Zelda LA", Hash_get(hash, "The Legend of Zelda - Link's Awakening.gb")); + TEST_ASSERT_EQUAL_STRING("Pokemon Red", Hash_get(hash, "Pokemon Red Version.gb")); + + // Non-aliased ROM returns NULL + TEST_ASSERT_NULL(Hash_get(hash, "Tetris.gb")); + + Hash_free(hash); +} + +void test_Array_integration_recent_games_list(void) { + Array* recents = Array_new(); + + // Simulate adding recently played games (newest first) + Array_unshift(recents, strdup("/Roms/GB/mario.gb")); + Array_unshift(recents, strdup("/Roms/NES/zelda.nes")); + Array_unshift(recents, strdup("/Roms/SNES/metroid.smc")); + + // Most recent should be first + TEST_ASSERT_EQUAL_INT(3, recents->count); + TEST_ASSERT_EQUAL_STRING("/Roms/SNES/metroid.smc", (char*)recents->items[0]); + TEST_ASSERT_EQUAL_STRING("/Roms/NES/zelda.nes", (char*)recents->items[1]); + TEST_ASSERT_EQUAL_STRING("/Roms/GB/mario.gb", (char*)recents->items[2]); + + StringArray_free(recents); +} + +/////////////////////////////// +// Test runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // Array basic lifecycle + RUN_TEST(test_Array_new_creates_empty_array); + RUN_TEST(test_Array_free_cleans_up); + + // Array_push + RUN_TEST(test_Array_push_single_item); + RUN_TEST(test_Array_push_multiple_items); + RUN_TEST(test_Array_push_grows_capacity); + + // Array_pop + RUN_TEST(test_Array_pop_returns_last_item); + RUN_TEST(test_Array_pop_empty_array_returns_null); + RUN_TEST(test_Array_pop_all_items); + + // Array_unshift + RUN_TEST(test_Array_unshift_empty_array); + RUN_TEST(test_Array_unshift_shifts_existing_items); + RUN_TEST(test_Array_unshift_multiple_times); + + // Array_reverse + RUN_TEST(test_Array_reverse_empty_array); + RUN_TEST(test_Array_reverse_single_item); + RUN_TEST(test_Array_reverse_even_count); + RUN_TEST(test_Array_reverse_odd_count); + + // StringArray + RUN_TEST(test_StringArray_indexOf_finds_string); + RUN_TEST(test_StringArray_indexOf_not_found); + RUN_TEST(test_StringArray_indexOf_empty_array); + RUN_TEST(test_StringArray_free_frees_strings); + + // Hash basic lifecycle + RUN_TEST(test_Hash_new_creates_empty_hash); + RUN_TEST(test_Hash_free_cleans_up); + + // Hash_set + RUN_TEST(test_Hash_set_single_entry); + RUN_TEST(test_Hash_set_multiple_entries); + RUN_TEST(test_Hash_set_allows_duplicate_keys); + + // Hash_get + RUN_TEST(test_Hash_get_retrieves_value); + RUN_TEST(test_Hash_get_not_found_returns_null); + RUN_TEST(test_Hash_get_empty_hash_returns_null); + RUN_TEST(test_Hash_get_with_duplicate_keys_returns_first); + + // Integration tests + RUN_TEST(test_Hash_integration_rom_alias_map); + RUN_TEST(test_Array_integration_recent_games_list); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_directory_utils.c b/tests/unit/all/common/test_directory_utils.c new file mode 100644 index 00000000..a247ed9e --- /dev/null +++ b/tests/unit/all/common/test_directory_utils.c @@ -0,0 +1,183 @@ +/** + * test_directory_utils.c - Tests for directory utility functions + * + * Tests directory operations using real temp directories. + * These tests are OS-portable and work in both Docker and native environments. + */ + +#include "../../../support/unity/unity.h" +#include "../../../../workspace/all/common/minui_file_utils.h" + +#include +#include +#include +#include + +void setUp(void) { + // Nothing to set up +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// hasNonHiddenFiles Tests +/////////////////////////////// + +void test_MinUI_hasNonHiddenFiles_empty_directory(void) { + // Create temp directory + char temp_dir[] = "/tmp/dirtest_XXXXXX"; + TEST_ASSERT_NOT_NULL(mkdtemp(temp_dir)); + + // Empty directory should return 0 + int result = MinUI_hasNonHiddenFiles(temp_dir); + TEST_ASSERT_FALSE(result); + + // Cleanup + rmdir(temp_dir); +} + +void test_MinUI_hasNonHiddenFiles_with_visible_file(void) { + char temp_dir[] = "/tmp/dirtest_XXXXXX"; + TEST_ASSERT_NOT_NULL(mkdtemp(temp_dir)); + + // Create a visible file + char file_path[512]; + sprintf(file_path, "%s/visible.txt", temp_dir); + FILE* f = fopen(file_path, "w"); + fputs("test", f); + fclose(f); + + // Should return 1 + int result = MinUI_hasNonHiddenFiles(temp_dir); + TEST_ASSERT_TRUE(result); + + // Cleanup + unlink(file_path); + rmdir(temp_dir); +} + +void test_MinUI_hasNonHiddenFiles_only_hidden_files(void) { + char temp_dir[] = "/tmp/dirtest_XXXXXX"; + TEST_ASSERT_NOT_NULL(mkdtemp(temp_dir)); + + // Create hidden files + char file1[512], file2[512]; + sprintf(file1, "%s/.hidden1", temp_dir); + sprintf(file2, "%s/.hidden2", temp_dir); + + FILE* f1 = fopen(file1, "w"); + fputs("test", f1); + fclose(f1); + + FILE* f2 = fopen(file2, "w"); + fputs("test", f2); + fclose(f2); + + // Should return 0 (only hidden files) + int result = MinUI_hasNonHiddenFiles(temp_dir); + TEST_ASSERT_FALSE(result); + + // Cleanup + unlink(file1); + unlink(file2); + rmdir(temp_dir); +} + +void test_MinUI_hasNonHiddenFiles_mixed_files(void) { + char temp_dir[] = "/tmp/dirtest_XXXXXX"; + TEST_ASSERT_NOT_NULL(mkdtemp(temp_dir)); + + // Create both hidden and visible files + char hidden[512], visible[512]; + sprintf(hidden, "%s/.hidden", temp_dir); + sprintf(visible, "%s/visible.txt", temp_dir); + + FILE* f1 = fopen(hidden, "w"); + fputs("test", f1); + fclose(f1); + + FILE* f2 = fopen(visible, "w"); + fputs("test", f2); + fclose(f2); + + // Should return 1 (has at least one visible file) + int result = MinUI_hasNonHiddenFiles(temp_dir); + TEST_ASSERT_TRUE(result); + + // Cleanup + unlink(hidden); + unlink(visible); + rmdir(temp_dir); +} + +void test_MinUI_hasNonHiddenFiles_nonexistent_directory(void) { + // Should return 0 for nonexistent directory + int result = MinUI_hasNonHiddenFiles("/nonexistent/path/to/dir"); + TEST_ASSERT_FALSE(result); +} + +void test_MinUI_hasNonHiddenFiles_with_subdirectories(void) { + char temp_dir[] = "/tmp/dirtest_XXXXXX"; + TEST_ASSERT_NOT_NULL(mkdtemp(temp_dir)); + + // Create a subdirectory (not hidden) + char subdir[512]; + sprintf(subdir, "%s/subdir", temp_dir); + mkdir(subdir, 0755); + + // Should return 1 (subdirectory counts as non-hidden entry) + int result = MinUI_hasNonHiddenFiles(temp_dir); + TEST_ASSERT_TRUE(result); + + // Cleanup + rmdir(subdir); + rmdir(temp_dir); +} + +void test_MinUI_hasNonHiddenFiles_with_dotfiles(void) { + char temp_dir[] = "/tmp/dirtest_XXXXXX"; + TEST_ASSERT_NOT_NULL(mkdtemp(temp_dir)); + + // Create dotfiles that should be hidden + char file1[512], file2[512], file3[512]; + sprintf(file1, "%s/.DS_Store", temp_dir); + sprintf(file2, "%s/.gitignore", temp_dir); + sprintf(file3, "%s/.config", temp_dir); + + FILE* f1 = fopen(file1, "w"); + fclose(f1); + FILE* f2 = fopen(file2, "w"); + fclose(f2); + FILE* f3 = fopen(file3, "w"); + fclose(f3); + + // Should return 0 (all hidden) + int result = MinUI_hasNonHiddenFiles(temp_dir); + TEST_ASSERT_FALSE(result); + + // Cleanup + unlink(file1); + unlink(file2); + unlink(file3); + rmdir(temp_dir); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_MinUI_hasNonHiddenFiles_empty_directory); + RUN_TEST(test_MinUI_hasNonHiddenFiles_with_visible_file); + RUN_TEST(test_MinUI_hasNonHiddenFiles_only_hidden_files); + RUN_TEST(test_MinUI_hasNonHiddenFiles_mixed_files); + RUN_TEST(test_MinUI_hasNonHiddenFiles_nonexistent_directory); + RUN_TEST(test_MinUI_hasNonHiddenFiles_with_subdirectories); + RUN_TEST(test_MinUI_hasNonHiddenFiles_with_dotfiles); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_gfx_text.c b/tests/unit/all/common/test_gfx_text.c new file mode 100644 index 00000000..10fd10cb --- /dev/null +++ b/tests/unit/all/common/test_gfx_text.c @@ -0,0 +1,471 @@ +/** + * test_gfx_text.c - Unit tests for GFX text utilities + * + * Tests text truncation and wrapping algorithms extracted from api.c. + * Uses fff framework to mock TTF_SizeUTF8 font measurement function. + * + * Test coverage: + * - GFX_truncateText() - Text truncation with ellipsis + * - GFX_wrapText() - Multi-line text wrapping + */ + +#include "../../../support/unity/unity.h" +#include "../../../support/fff/fff.h" +#include "../../../support/sdl_stubs.h" +#include "../../../support/sdl_fakes.h" +#include "../../../../workspace/all/common/gfx_text.h" + +#include + +DEFINE_FFF_GLOBALS; + +// Mock font for testing +static TTF_Font mock_font = {.point_size = 16, .style = 0}; + +/////////////////////////////// +// Custom TTF_SizeUTF8 Mock +/////////////////////////////// + +/** + * Simple width calculation: 10 pixels per character + * This makes testing predictable and easy to understand + */ +int mock_TTF_SizeUTF8(TTF_Font* font, const char* text, int* w, int* h) { + if (w) + *w = strlen(text) * 10; // 10 pixels per char + if (h) + *h = font->point_size; + return 0; +} + +void setUp(void) { + // Reset fff fakes + RESET_FAKE(TTF_SizeUTF8); + FFF_RESET_HISTORY(); + + // Use our simple mock by default + TTF_SizeUTF8_fake.custom_fake = mock_TTF_SizeUTF8; +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// GFX_truncateText() Tests +/////////////////////////////// + +void test_truncateText_no_truncation_needed(void) { + char output[256]; + + // "Hello" = 50px, max_width = 100px, no truncation needed + int width = GFX_truncateText(&mock_font, "Hello", output, 100, 0); + + TEST_ASSERT_EQUAL_STRING("Hello", output); + TEST_ASSERT_EQUAL_INT(50, width); // 5 chars * 10px +} + +void test_truncateText_with_padding(void) { + char output[256]; + + // "Hello" = 50px + 20px padding = 70px, max_width = 100px + int width = GFX_truncateText(&mock_font, "Hello", output, 100, 20); + + TEST_ASSERT_EQUAL_STRING("Hello", output); + TEST_ASSERT_EQUAL_INT(70, width); // 50px + 20px padding +} + +void test_truncateText_truncates_long_text(void) { + char output[256]; + + // "The Legend of Zelda" = 190px, max_width = 100px + // Should truncate to fit + int width = GFX_truncateText(&mock_font, "The Legend of Zelda", output, 100, 0); + + // Result should end with "..." + TEST_ASSERT_TRUE(strstr(output, "...") != NULL); + TEST_ASSERT_LESS_OR_EQUAL(100, width); +} + +void test_truncateText_result_ends_with_ellipsis(void) { + char output[256]; + + GFX_truncateText(&mock_font, "Very Long Text That Needs Truncating", output, 80, 0); + + // Check that output ends with "..." + int len = strlen(output); + TEST_ASSERT_GREATER_THAN(3, len); + TEST_ASSERT_EQUAL_STRING("...", &output[len - 3]); +} + +void test_truncateText_respects_max_width_with_padding(void) { + char output[256]; + + // With padding, should still fit within max_width + int width = GFX_truncateText(&mock_font, "A Very Long String Indeed", output, 100, 15); + + TEST_ASSERT_LESS_OR_EQUAL(100, width); + TEST_ASSERT_TRUE(strstr(output, "...") != NULL); +} + +void test_truncateText_short_text_unchanged(void) { + char output[256]; + + int width = GFX_truncateText(&mock_font, "Hi", output, 500, 0); + + TEST_ASSERT_EQUAL_STRING("Hi", output); + TEST_ASSERT_EQUAL_INT(20, width); // 2 chars * 10px +} + +void test_truncateText_exact_fit_no_truncation(void) { + char output[256]; + + // "12345" = 50px exactly + int width = GFX_truncateText(&mock_font, "12345", output, 50, 0); + + TEST_ASSERT_EQUAL_STRING("12345", output); + TEST_ASSERT_EQUAL_INT(50, width); +} + +void test_truncateText_one_char_over_truncates(void) { + char output[256]; + + // "123456" = 60px, max = 50px, needs truncation + int width = GFX_truncateText(&mock_font, "123456", output, 50, 0); + + TEST_ASSERT_TRUE(strstr(output, "...") != NULL); + TEST_ASSERT_LESS_OR_EQUAL(50, width); +} + +/////////////////////////////// +// GFX_wrapText() Tests +/////////////////////////////// + +void test_wrapText_null_string_returns_zero(void) { + int width = GFX_wrapText(&mock_font, NULL, 100, 0); + + TEST_ASSERT_EQUAL_INT(0, width); +} + +void test_wrapText_short_text_no_wrapping(void) { + char text[256] = "Hello"; + + int width = GFX_wrapText(&mock_font, text, 100, 0); + + // Text fits, no newlines added + TEST_ASSERT_EQUAL_STRING("Hello", text); + TEST_ASSERT_EQUAL_INT(50, width); // 5 chars * 10px +} + +void test_wrapText_wraps_at_space(void) { + char text[256] = "Hello World Test"; + + // "Hello World Test" = 160px, max = 100px + // Should wrap at spaces + int width = GFX_wrapText(&mock_font, text, 100, 0); + + // Should have newlines inserted + TEST_ASSERT_TRUE(strchr(text, '\n') != NULL); + // Width should be reasonable (widest line, possibly with truncation) + TEST_ASSERT_GREATER_THAN(0, width); +} + +void test_wrapText_modifies_string_in_place(void) { + char text[256] = "The quick brown fox jumps"; + + char* original_ptr = text; + GFX_wrapText(&mock_font, text, 80, 0); + + // Should have modified the same buffer + TEST_ASSERT_EQUAL_PTR(original_ptr, text); + // Should contain newlines now + TEST_ASSERT_TRUE(strchr(text, '\n') != NULL); +} + +void test_wrapText_respects_max_lines(void) { + char text[256] = "One Two Three Four Five Six Seven Eight"; + + // Limit to 3 lines + GFX_wrapText(&mock_font, text, 50, 3); + + // Count newlines + int newline_count = 0; + for (char* p = text; *p; p++) { + if (*p == '\n') + newline_count++; + } + + // Should have at most 2 newlines (for 3 lines total) + TEST_ASSERT_LESS_OR_EQUAL(2, newline_count); +} + +void test_wrapText_zero_max_lines_means_unlimited(void) { + char text[256] = "A B C D E F G H I J K L M N O P"; + + // max_lines = 0 means no limit + GFX_wrapText(&mock_font, text, 30, 0); + + // Should wrap as needed without line limit + // Just verify it doesn't crash + TEST_PASS(); +} + +void test_wrapText_returns_widest_line_width(void) { + char text[256] = "Short VeryLongWord X"; + + // Should return width of widest line + int width = GFX_wrapText(&mock_font, text, 100, 0); + + // Width should be the longest line (may include truncated long words) + TEST_ASSERT_GREATER_THAN(0, width); + // No upper bound assertion - function returns actual widest line +} + +void test_wrapText_single_long_word_truncates(void) { + char text[256] = "Supercalifragilisticexpialidocious"; + + // Single word too long should get truncated with "..." + GFX_wrapText(&mock_font, text, 100, 0); + + TEST_ASSERT_TRUE(strstr(text, "...") != NULL); +} + +void test_wrapText_preserves_multiple_spaces(void) { + char text[256] = "Hello World"; + + // Has two spaces between words + GFX_wrapText(&mock_font, text, 200, 0); + + // If no wrapping occurred, spaces should be unchanged + if (strchr(text, '\n') == NULL) { + TEST_ASSERT_TRUE(strstr(text, " ") != NULL); + } +} + +void test_wrapText_last_line_truncated_if_too_long(void) { + char text[256] = "OK ThisIsAVeryLongWordThatWillBeTruncated"; + + GFX_wrapText(&mock_font, text, 80, 0); + + // Last line should be truncated with "..." + TEST_ASSERT_TRUE(strstr(text, "...") != NULL); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_truncateText_integration_rom_name(void) { + char output[256]; + + // Simulate ROM name truncation + GFX_truncateText(&mock_font, "The Legend of Zelda - A Link to the Past", output, 150, 5); + + // Should be truncated and end with "..." + TEST_ASSERT_TRUE(strstr(output, "...") != NULL); + TEST_ASSERT_LESS_OR_EQUAL(strlen("The Legend of Zelda - A Link to the Past"), + strlen(output)); +} + +void test_wrapText_integration_dialog_text(void) { + char text[256] = "This is a long dialog message that should wrap nicely across " + "multiple lines for display"; + + int width = GFX_wrapText(&mock_font, text, 200, 4); + + // Should have wrapped the text + TEST_ASSERT_TRUE(strchr(text, '\n') != NULL); + // Width is the widest line (may exceed max_width if single word is long) + TEST_ASSERT_GREATER_THAN(0, width); + + // Should not exceed max lines (4 lines = 3 newlines max) + int newlines = 0; + for (char* p = text; *p; p++) { + if (*p == '\n') + newlines++; + } + TEST_ASSERT_LESS_OR_EQUAL(3, newlines); +} + +void test_wrapText_integration_no_spaces(void) { + char text[256] = "NoSpacesInThisTextAtAll"; + + GFX_wrapText(&mock_font, text, 100, 0); + + // Since no spaces, should truncate with "..." + TEST_ASSERT_TRUE(strstr(text, "...") != NULL); +} + +/////////////////////////////// +// Edge Cases +/////////////////////////////// + +void test_truncateText_empty_string(void) { + char output[256]; + + int width = GFX_truncateText(&mock_font, "", output, 100, 0); + + TEST_ASSERT_EQUAL_STRING("", output); + TEST_ASSERT_EQUAL_INT(0, width); +} + +void test_wrapText_empty_string(void) { + char text[256] = ""; + + int width = GFX_wrapText(&mock_font, text, 100, 0); + + TEST_ASSERT_EQUAL_STRING("", text); + TEST_ASSERT_EQUAL_INT(0, width); +} + +void test_wrapText_only_spaces(void) { + char text[256] = " "; + + int width = GFX_wrapText(&mock_font, text, 100, 0); + + // Should handle gracefully + TEST_ASSERT_GREATER_OR_EQUAL(0, width); +} + +/////////////////////////////// +// GFX_sizeText() Tests +/////////////////////////////// + +void test_sizeText_single_line(void) { + char text[256] = "Hello World"; + int w, h; + + // "Hello World" = 110px (11 chars * 10px), leading = 16px + GFX_sizeText(&mock_font, text, 16, &w, &h); + + TEST_ASSERT_EQUAL_INT(110, w); // 11 chars * 10px + TEST_ASSERT_EQUAL_INT(16, h); // 1 line * 16px leading +} + +void test_sizeText_multiple_lines(void) { + char text[256] = "Line 1\nLine 2\nLine 3"; + int w, h; + + // 3 lines, each "Line X" = 60px, leading = 20px + GFX_sizeText(&mock_font, text, 20, &w, &h); + + TEST_ASSERT_EQUAL_INT(60, w); // 6 chars * 10px + TEST_ASSERT_EQUAL_INT(60, h); // 3 lines * 20px leading +} + +void test_sizeText_finds_widest_line(void) { + char text[256] = "Short\nThis is a much longer line\nShort"; + int w, h; + + GFX_sizeText(&mock_font, text, 16, &w, &h); + + // Widest line is "This is a much longer line" = 260px (26 chars) + TEST_ASSERT_EQUAL_INT(260, w); // 26 chars * 10px + TEST_ASSERT_EQUAL_INT(48, h); // 3 lines * 16px +} + +void test_sizeText_empty_string(void) { + char text[256] = ""; + int w, h; + + GFX_sizeText(&mock_font, text, 16, &w, &h); + + TEST_ASSERT_EQUAL_INT(0, w); + TEST_ASSERT_EQUAL_INT(16, h); // 1 line (empty) * 16px +} + +void test_sizeText_empty_lines_in_middle(void) { + char text[256] = "First\n\nThird"; + int w, h; + + GFX_sizeText(&mock_font, text, 18, &w, &h); + + // 3 lines (including empty middle line) + TEST_ASSERT_EQUAL_INT(50, w); // "First" or "Third" = 50px + TEST_ASSERT_EQUAL_INT(54, h); // 3 lines * 18px +} + +void test_sizeText_trailing_newline(void) { + char text[256] = "Line 1\nLine 2\n"; + int w, h; + + GFX_sizeText(&mock_font, text, 16, &w, &h); + + // Should have 3 lines (last is empty) + TEST_ASSERT_EQUAL_INT(60, w); // "Line X" = 60px + TEST_ASSERT_EQUAL_INT(48, h); // 3 lines * 16px +} + +void test_sizeText_different_leading(void) { + char text[256] = "A\nB\nC\nD"; + int w, h; + + // Test with large leading + GFX_sizeText(&mock_font, text, 32, &w, &h); + + TEST_ASSERT_EQUAL_INT(10, w); // 1 char * 10px + TEST_ASSERT_EQUAL_INT(128, h); // 4 lines * 32px +} + +void test_sizeText_integration_dialog_box(void) { + char text[256] = "Game saved!\nContinue playing?"; + int w, h; + + GFX_sizeText(&mock_font, text, 20, &w, &h); + + // "Continue playing?" = 170px is longer + TEST_ASSERT_EQUAL_INT(170, w); // 17 chars * 10px + TEST_ASSERT_EQUAL_INT(40, h); // 2 lines * 20px +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // GFX_truncateText tests + RUN_TEST(test_truncateText_no_truncation_needed); + RUN_TEST(test_truncateText_with_padding); + RUN_TEST(test_truncateText_truncates_long_text); + RUN_TEST(test_truncateText_result_ends_with_ellipsis); + RUN_TEST(test_truncateText_respects_max_width_with_padding); + RUN_TEST(test_truncateText_short_text_unchanged); + RUN_TEST(test_truncateText_exact_fit_no_truncation); + RUN_TEST(test_truncateText_one_char_over_truncates); + + // GFX_wrapText tests + RUN_TEST(test_wrapText_null_string_returns_zero); + RUN_TEST(test_wrapText_short_text_no_wrapping); + RUN_TEST(test_wrapText_wraps_at_space); + RUN_TEST(test_wrapText_modifies_string_in_place); + RUN_TEST(test_wrapText_respects_max_lines); + RUN_TEST(test_wrapText_zero_max_lines_means_unlimited); + RUN_TEST(test_wrapText_returns_widest_line_width); + RUN_TEST(test_wrapText_single_long_word_truncates); + RUN_TEST(test_wrapText_preserves_multiple_spaces); + RUN_TEST(test_wrapText_last_line_truncated_if_too_long); + + // GFX_sizeText tests + RUN_TEST(test_sizeText_single_line); + RUN_TEST(test_sizeText_multiple_lines); + RUN_TEST(test_sizeText_finds_widest_line); + RUN_TEST(test_sizeText_empty_string); + RUN_TEST(test_sizeText_empty_lines_in_middle); + RUN_TEST(test_sizeText_trailing_newline); + RUN_TEST(test_sizeText_different_leading); + RUN_TEST(test_sizeText_integration_dialog_box); + + // Integration tests + RUN_TEST(test_truncateText_integration_rom_name); + RUN_TEST(test_wrapText_integration_dialog_text); + RUN_TEST(test_wrapText_integration_no_spaces); + + // Edge cases + RUN_TEST(test_truncateText_empty_string); + RUN_TEST(test_wrapText_empty_string); + RUN_TEST(test_wrapText_only_spaces); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_m3u_parser.c b/tests/unit/all/common/test_m3u_parser.c new file mode 100644 index 00000000..df8d6bf1 --- /dev/null +++ b/tests/unit/all/common/test_m3u_parser.c @@ -0,0 +1,369 @@ +/** + * test_m3u_parser.c - Unit tests for M3U playlist parser + * + * Tests M3U parsing logic extracted from minui.c. + * Uses file system mocking to test file reading without actual files. + * + * Test coverage: + * - Basic M3U parsing (single disc, multiple discs) + * - Empty line handling + * - Non-existent files + * - Path construction (relative paths from M3U directory) + * - Edge cases (empty M3U, missing disc files) + * + * Note: Uses GCC --wrap for file mocking (Docker-only, won't run natively on macOS) + */ + +#include "../../../support/unity/unity.h" +#include "../../../support/fs_mocks.h" +#include "../../../../workspace/all/common/m3u_parser.h" + +#include + +void setUp(void) { + mock_fs_reset(); +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// Basic M3U Parsing Tests +/////////////////////////////// + +void test_getFirstDisc_single_disc_m3u(void) { + // Create M3U file with one disc + mock_fs_add_file("/Roms/PS1/FF7.m3u", "FF7 (Disc 1).bin\n"); + mock_fs_add_file("/Roms/PS1/FF7 (Disc 1).bin", "disc data"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/FF7.m3u", disc_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF7 (Disc 1).bin", disc_path); +} + +void test_getFirstDisc_multi_disc_m3u_returns_first(void) { + // Create M3U with multiple discs + mock_fs_add_file("/Roms/PS1/Game.m3u", + "Game (Disc 1).bin\n" + "Game (Disc 2).bin\n" + "Game (Disc 3).bin\n"); + mock_fs_add_file("/Roms/PS1/Game (Disc 1).bin", "disc 1"); + mock_fs_add_file("/Roms/PS1/Game (Disc 2).bin", "disc 2"); + mock_fs_add_file("/Roms/PS1/Game (Disc 3).bin", "disc 3"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/Game.m3u", disc_path); + + TEST_ASSERT_TRUE(result); + // Should return FIRST disc only + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/Game (Disc 1).bin", disc_path); +} + +void test_getFirstDisc_skips_empty_lines(void) { + // M3U with empty lines before first disc + mock_fs_add_file("/Roms/PS1/Test.m3u", + "\n" + "\n" + "Test Disc.bin\n"); + mock_fs_add_file("/Roms/PS1/Test Disc.bin", "data"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/Test.m3u", disc_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/Test Disc.bin", disc_path); +} + +void test_getFirstDisc_handles_windows_newlines(void) { + // M3U created on Windows with \r\n line endings + mock_fs_add_file("/Roms/PS1/Game.m3u", "Disc.bin\r\n"); + mock_fs_add_file("/Roms/PS1/Disc.bin", "data"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/Game.m3u", disc_path); + + TEST_ASSERT_TRUE(result); + // normalizeNewline should have handled \r\n + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/Disc.bin", disc_path); +} + +/////////////////////////////// +// Path Construction Tests +/////////////////////////////// + +void test_getFirstDisc_constructs_full_path(void) { + // Disc path is relative to M3U location + mock_fs_add_file("/Games/Saturn/Panzer.m3u", "panzer1.cue\n"); + mock_fs_add_file("/Games/Saturn/panzer1.cue", "cue data"); + + char disc_path[256]; + M3U_getFirstDisc("/Games/Saturn/Panzer.m3u", disc_path); + + // Should prepend directory path + TEST_ASSERT_EQUAL_STRING("/Games/Saturn/panzer1.cue", disc_path); +} + +void test_getFirstDisc_deep_directory(void) { + mock_fs_add_file("/a/b/c/d/game.m3u", "disc.iso\n"); + mock_fs_add_file("/a/b/c/d/disc.iso", "data"); + + char disc_path[256]; + M3U_getFirstDisc("/a/b/c/d/game.m3u", disc_path); + + TEST_ASSERT_EQUAL_STRING("/a/b/c/d/disc.iso", disc_path); +} + +void test_getFirstDisc_subdirectory_in_disc_path(void) { + // Some M3Us reference discs in subdirectories + mock_fs_add_file("/Roms/PS1/FF7.m3u", "discs/FF7_1.bin\n"); + mock_fs_add_file("/Roms/PS1/discs/FF7_1.bin", "data"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/FF7.m3u", disc_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/discs/FF7_1.bin", disc_path); +} + +/////////////////////////////// +// Error Cases +/////////////////////////////// + +void test_getFirstDisc_m3u_file_not_found(void) { + // M3U doesn't exist + char disc_path[256]; + int result = M3U_getFirstDisc("/nonexistent.m3u", disc_path); + + TEST_ASSERT_FALSE(result); +} + +void test_getFirstDisc_disc_file_not_found(void) { + // M3U exists but disc doesn't + mock_fs_add_file("/Roms/PS1/Game.m3u", "missing_disc.bin\n"); + // Note: not creating the disc file + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/Game.m3u", disc_path); + + TEST_ASSERT_FALSE(result); +} + +void test_getFirstDisc_empty_m3u_file(void) { + // M3U exists but is empty + mock_fs_add_file("/Roms/PS1/Empty.m3u", ""); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/Empty.m3u", disc_path); + + TEST_ASSERT_FALSE(result); +} + +void test_getFirstDisc_m3u_only_empty_lines(void) { + // M3U with only whitespace/newlines + mock_fs_add_file("/Roms/PS1/Blank.m3u", "\n\n\n\n"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/PS1/Blank.m3u", disc_path); + + TEST_ASSERT_FALSE(result); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_getFirstDisc_realistic_final_fantasy(void) { + // Realistic Final Fantasy VII scenario + mock_fs_add_file("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA).m3u", + "Final Fantasy VII (USA) (Disc 1).bin\n" + "Final Fantasy VII (USA) (Disc 2).bin\n" + "Final Fantasy VII (USA) (Disc 3).bin\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA) (Disc 1).bin", "disc"); + mock_fs_add_file("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA) (Disc 2).bin", "disc"); + mock_fs_add_file("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA) (Disc 3).bin", "disc"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA).m3u", disc_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA) (Disc 1).bin", + disc_path); +} + +void test_getFirstDisc_with_cue_files(void) { + // Some games use .cue files instead of .bin + mock_fs_add_file("/Roms/Saturn/Panzer Dragoon.m3u", + "Panzer Dragoon (Disc 1).cue\n" + "Panzer Dragoon (Disc 2).cue\n"); + + mock_fs_add_file("/Roms/Saturn/Panzer Dragoon (Disc 1).cue", "cue"); + mock_fs_add_file("/Roms/Saturn/Panzer Dragoon (Disc 2).cue", "cue"); + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/Saturn/Panzer Dragoon.m3u", disc_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/Roms/Saturn/Panzer Dragoon (Disc 1).cue", disc_path); +} + +void test_getFirstDisc_stops_at_first_valid_disc(void) { + // First line references missing file, second line has valid disc + mock_fs_add_file("/Roms/Test.m3u", + "missing.bin\n" + "exists.bin\n"); + + mock_fs_add_file("/Roms/exists.bin", "data"); + // Note: not creating missing.bin + + char disc_path[256]; + int result = M3U_getFirstDisc("/Roms/Test.m3u", disc_path); + + // Should return false because first disc doesn't exist + // (Function only checks first non-empty line) + TEST_ASSERT_FALSE(result); + TEST_ASSERT_EQUAL_STRING("/Roms/missing.bin", disc_path); +} + +/////////////////////////////// +// M3U_getAllDiscs() Tests +/////////////////////////////// + +void test_getAllDiscs_single_disc(void) { + mock_fs_add_file("/Roms/PS1/Game.m3u", "game.bin\n"); + mock_fs_add_file("/Roms/PS1/game.bin", "disc"); + + int count; + M3U_Disc** discs = M3U_getAllDiscs("/Roms/PS1/Game.m3u", &count); + + TEST_ASSERT_EQUAL_INT(1, count); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/game.bin", discs[0]->path); + TEST_ASSERT_EQUAL_STRING("Disc 1", discs[0]->name); + TEST_ASSERT_EQUAL_INT(1, discs[0]->disc_number); + + M3U_freeDiscs(discs, count); +} + +void test_getAllDiscs_multiple_discs(void) { + mock_fs_add_file("/Roms/PS1/FF7.m3u", + "FF7 (Disc 1).bin\n" + "FF7 (Disc 2).bin\n" + "FF7 (Disc 3).bin\n"); + mock_fs_add_file("/Roms/PS1/FF7 (Disc 1).bin", "d1"); + mock_fs_add_file("/Roms/PS1/FF7 (Disc 2).bin", "d2"); + mock_fs_add_file("/Roms/PS1/FF7 (Disc 3).bin", "d3"); + + int count; + M3U_Disc** discs = M3U_getAllDiscs("/Roms/PS1/FF7.m3u", &count); + + TEST_ASSERT_EQUAL_INT(3, count); + + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF7 (Disc 1).bin", discs[0]->path); + TEST_ASSERT_EQUAL_STRING("Disc 1", discs[0]->name); + TEST_ASSERT_EQUAL_INT(1, discs[0]->disc_number); + + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF7 (Disc 2).bin", discs[1]->path); + TEST_ASSERT_EQUAL_STRING("Disc 2", discs[1]->name); + + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF7 (Disc 3).bin", discs[2]->path); + TEST_ASSERT_EQUAL_STRING("Disc 3", discs[2]->name); + + M3U_freeDiscs(discs, count); +} + +void test_getAllDiscs_skips_missing_discs(void) { + mock_fs_add_file("/Roms/Game.m3u", + "disc1.bin\n" + "disc2.bin\n" + "disc3.bin\n"); + // Only create disc1 and disc3 + mock_fs_add_file("/Roms/disc1.bin", "d1"); + mock_fs_add_file("/Roms/disc3.bin", "d3"); + + int count; + M3U_Disc** discs = M3U_getAllDiscs("/Roms/Game.m3u", &count); + + // Should only return discs that exist (1 and 3) + // But numbered sequentially as 1 and 2 + TEST_ASSERT_EQUAL_INT(2, count); + TEST_ASSERT_EQUAL_STRING("/Roms/disc1.bin", discs[0]->path); + TEST_ASSERT_EQUAL_STRING("Disc 1", discs[0]->name); + TEST_ASSERT_EQUAL_STRING("/Roms/disc3.bin", discs[1]->path); + TEST_ASSERT_EQUAL_STRING("Disc 2", discs[1]->name); + + M3U_freeDiscs(discs, count); +} + +void test_getAllDiscs_skips_empty_lines(void) { + mock_fs_add_file("/Test.m3u", + "\n" + "d1.bin\n" + "\n" + "d2.bin\n"); + mock_fs_add_file("/d1.bin", "disc"); + mock_fs_add_file("/d2.bin", "disc"); + + int count; + M3U_Disc** discs = M3U_getAllDiscs("/Test.m3u", &count); + + TEST_ASSERT_EQUAL_INT(2, count); + + M3U_freeDiscs(discs, count); +} + +void test_getAllDiscs_empty_m3u_returns_zero(void) { + mock_fs_add_file("/Empty.m3u", ""); + + int count; + M3U_Disc** discs = M3U_getAllDiscs("/Empty.m3u", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + M3U_freeDiscs(discs, count); +} + +void test_getAllDiscs_m3u_not_found_returns_zero(void) { + int count; + M3U_Disc** discs = M3U_getAllDiscs("/nonexistent.m3u", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + M3U_freeDiscs(discs, count); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // getFirstDisc() tests + RUN_TEST(test_getFirstDisc_single_disc_m3u); + RUN_TEST(test_getFirstDisc_multi_disc_m3u_returns_first); + RUN_TEST(test_getFirstDisc_skips_empty_lines); + RUN_TEST(test_getFirstDisc_handles_windows_newlines); + RUN_TEST(test_getFirstDisc_constructs_full_path); + RUN_TEST(test_getFirstDisc_deep_directory); + RUN_TEST(test_getFirstDisc_subdirectory_in_disc_path); + RUN_TEST(test_getFirstDisc_m3u_file_not_found); + RUN_TEST(test_getFirstDisc_disc_file_not_found); + RUN_TEST(test_getFirstDisc_empty_m3u_file); + RUN_TEST(test_getFirstDisc_m3u_only_empty_lines); + RUN_TEST(test_getFirstDisc_realistic_final_fantasy); + RUN_TEST(test_getFirstDisc_with_cue_files); + RUN_TEST(test_getFirstDisc_stops_at_first_valid_disc); + + // getAllDiscs() tests + RUN_TEST(test_getAllDiscs_single_disc); + RUN_TEST(test_getAllDiscs_multiple_discs); + RUN_TEST(test_getAllDiscs_skips_missing_discs); + RUN_TEST(test_getAllDiscs_skips_empty_lines); + RUN_TEST(test_getAllDiscs_empty_m3u_returns_zero); + RUN_TEST(test_getAllDiscs_m3u_not_found_returns_zero); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_map_parser.c b/tests/unit/all/common/test_map_parser.c new file mode 100644 index 00000000..ef09eb06 --- /dev/null +++ b/tests/unit/all/common/test_map_parser.c @@ -0,0 +1,365 @@ +/** + * test_map_parser.c - Unit tests for map.txt parser + * + * Tests ROM display name aliasing logic extracted from minarch.c/minui.c. + * Uses file system mocking to test file reading without actual files. + * + * Test coverage: + * - Basic alias lookup (single entry, multiple entries) + * - Tab-delimited format parsing + * - Case sensitivity + * - Missing files/entries + * - Empty lines and malformed entries + * - Hidden ROMs (alias starts with '.') + * + * Note: Uses GCC --wrap for file mocking (Docker-only) + */ + +#include "../../../support/unity/unity.h" +#include "../../../support/fs_mocks.h" +#include "../../../../workspace/all/common/map_parser.h" + +#include + +void setUp(void) { + mock_fs_reset(); +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// Basic Alias Lookup Tests +/////////////////////////////// + +void test_getAlias_finds_single_entry(void) { + mock_fs_add_file("/Roms/GB/map.txt", "mario.gb\tSuper Mario Land\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/mario.gb", alias); + + TEST_ASSERT_EQUAL_STRING("Super Mario Land", alias); +} + +void test_getAlias_finds_entry_in_multi_line_map(void) { + mock_fs_add_file("/Roms/GB/map.txt", + "mario.gb\tSuper Mario Land\n" + "zelda.gb\tLink's Awakening\n" + "tetris.gb\tTetris\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/zelda.gb", alias); + + TEST_ASSERT_EQUAL_STRING("Link's Awakening", alias); +} + +void test_getAlias_finds_first_entry(void) { + mock_fs_add_file("/Roms/NES/map.txt", + "mario.nes\tSuper Mario Bros\n" + "zelda.nes\tThe Legend of Zelda\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/NES/mario.nes", alias); + + TEST_ASSERT_EQUAL_STRING("Super Mario Bros", alias); +} + +void test_getAlias_finds_last_entry(void) { + mock_fs_add_file("/Roms/NES/map.txt", + "mario.nes\tSuper Mario Bros\n" + "zelda.nes\tThe Legend of Zelda\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/NES/zelda.nes", alias); + + TEST_ASSERT_EQUAL_STRING("The Legend of Zelda", alias); +} + +/////////////////////////////// +// No Map / No Match Tests +/////////////////////////////// + +void test_getAlias_no_map_file_leaves_alias_unchanged(void) { + // No map.txt exists + char alias[256] = "Original Name"; + Map_getAlias("/Roms/GB/game.gb", alias); + + // Alias should be unchanged + TEST_ASSERT_EQUAL_STRING("Original Name", alias); +} + +void test_getAlias_rom_not_in_map_leaves_alias_unchanged(void) { + mock_fs_add_file("/Roms/GB/map.txt", "mario.gb\tSuper Mario Land\n"); + + char alias[256] = "Tetris"; + Map_getAlias("/Roms/GB/tetris.gb", alias); + + // Alias should be unchanged (tetris.gb not in map) + TEST_ASSERT_EQUAL_STRING("Tetris", alias); +} + +void test_getAlias_empty_map_file_leaves_alias_unchanged(void) { + mock_fs_add_file("/Roms/GB/map.txt", ""); + + char alias[256] = "Default"; + Map_getAlias("/Roms/GB/game.gb", alias); + + TEST_ASSERT_EQUAL_STRING("Default", alias); +} + +/////////////////////////////// +// Format Handling Tests +/////////////////////////////// + +void test_getAlias_skips_empty_lines(void) { + mock_fs_add_file("/Roms/GB/map.txt", + "\n" + "\n" + "mario.gb\tSuper Mario\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/mario.gb", alias); + + TEST_ASSERT_EQUAL_STRING("Super Mario", alias); +} + +void test_getAlias_handles_lines_without_tab(void) { + // Malformed entry (no tab separator) + mock_fs_add_file("/Roms/GB/map.txt", + "broken line without tab\n" + "mario.gb\tSuper Mario\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/mario.gb", alias); + + // Should skip broken line and find mario + TEST_ASSERT_EQUAL_STRING("Super Mario", alias); +} + +void test_getAlias_exact_match_required(void) { + mock_fs_add_file("/Roms/GB/map.txt", "mario.gb\tSuper Mario\n"); + + char alias[256] = "Original"; + + // Partial match should not work + Map_getAlias("/Roms/GB/mario2.gb", alias); + TEST_ASSERT_EQUAL_STRING("Original", alias); + + // Different case should not match (exactMatch is case-sensitive) + Map_getAlias("/Roms/GB/MARIO.GB", alias); + TEST_ASSERT_EQUAL_STRING("Original", alias); +} + +void test_getAlias_handles_windows_newlines(void) { + mock_fs_add_file("/Roms/GB/map.txt", "mario.gb\tSuper Mario\r\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/mario.gb", alias); + + TEST_ASSERT_EQUAL_STRING("Super Mario", alias); +} + +/////////////////////////////// +// Special Characters Tests +/////////////////////////////// + +void test_getAlias_with_special_characters_in_filename(void) { + mock_fs_add_file("/Roms/GB/map.txt", "game (USA) (v1.1).gb\tGame USA\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/game (USA) (v1.1).gb", alias); + + TEST_ASSERT_EQUAL_STRING("Game USA", alias); +} + +void test_getAlias_with_special_characters_in_alias(void) { + mock_fs_add_file("/Roms/GB/map.txt", "mario.gb\tSuper Mario™ - The Game!\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/mario.gb", alias); + + TEST_ASSERT_EQUAL_STRING("Super Mario™ - The Game!", alias); +} + +void test_getAlias_hidden_rom_starts_with_dot(void) { + mock_fs_add_file("/Roms/GB/map.txt", "hidden.gb\t.Hidden Game\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/hidden.gb", alias); + + // Should get the alias (even though it starts with '.') + TEST_ASSERT_EQUAL_STRING(".Hidden Game", alias); +} + +/////////////////////////////// +// Path Tests +/////////////////////////////// + +void test_getAlias_different_directories(void) { + mock_fs_add_file("/a/b/c/map.txt", "file.rom\tAliased Name\n"); + + char alias[256] = ""; + Map_getAlias("/a/b/c/file.rom", alias); + + TEST_ASSERT_EQUAL_STRING("Aliased Name", alias); +} + +void test_getAlias_deep_directory_structure(void) { + mock_fs_add_file("/mnt/SDCARD/Roms/PS1/RPG/map.txt", "ff7.bin\tFinal Fantasy VII\n"); + + char alias[256] = ""; + Map_getAlias("/mnt/SDCARD/Roms/PS1/RPG/ff7.bin", alias); + + TEST_ASSERT_EQUAL_STRING("Final Fantasy VII", alias); +} + +void test_getAlias_looks_in_rom_directory_not_parent(void) { + // map.txt in wrong location (parent directory) + mock_fs_add_file("/Roms/map.txt", "game.gb\tWrong Location\n"); + mock_fs_add_file("/Roms/GB/map.txt", "game.gb\tCorrect Location\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/GB/game.gb", alias); + + // Should use map.txt from /Roms/GB/, not /Roms/ + TEST_ASSERT_EQUAL_STRING("Correct Location", alias); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_getAlias_realistic_rom_library(void) { + // Real-world map.txt for Game Boy + mock_fs_add_file("/mnt/SDCARD/Roms/GB/map.txt", + "Super Mario Land (World).gb\tMario Land\n" + "The Legend of Zelda - Link's Awakening (USA, Europe) (Rev 2).gb\tZelda LA\n" + "Pokemon - Red Version (USA, Europe).gb\tPokemon Red\n" + "Tetris (World) (Rev 1).gb\tTetris\n"); + + char alias[256]; + + // Test each ROM + strcpy(alias, ""); + Map_getAlias("/mnt/SDCARD/Roms/GB/Super Mario Land (World).gb", alias); + TEST_ASSERT_EQUAL_STRING("Mario Land", alias); + + strcpy(alias, ""); + Map_getAlias("/mnt/SDCARD/Roms/GB/The Legend of Zelda - Link's Awakening (USA, Europe) (Rev 2).gb", alias); + TEST_ASSERT_EQUAL_STRING("Zelda LA", alias); + + strcpy(alias, ""); + Map_getAlias("/mnt/SDCARD/Roms/GB/Pokemon - Red Version (USA, Europe).gb", alias); + TEST_ASSERT_EQUAL_STRING("Pokemon Red", alias); +} + +void test_getAlias_multiple_roms_same_directory(void) { + mock_fs_add_file("/Roms/NES/map.txt", + "mario1.nes\tSuper Mario Bros\n" + "mario2.nes\tSuper Mario Bros 2\n" + "mario3.nes\tSuper Mario Bros 3\n"); + + char alias1[256] = ""; + char alias2[256] = ""; + char alias3[256] = ""; + + Map_getAlias("/Roms/NES/mario1.nes", alias1); + Map_getAlias("/Roms/NES/mario2.nes", alias2); + Map_getAlias("/Roms/NES/mario3.nes", alias3); + + TEST_ASSERT_EQUAL_STRING("Super Mario Bros", alias1); + TEST_ASSERT_EQUAL_STRING("Super Mario Bros 2", alias2); + TEST_ASSERT_EQUAL_STRING("Super Mario Bros 3", alias3); +} + +void test_getAlias_hidden_roms_workflow(void) { + // Map with both visible and hidden ROMs + mock_fs_add_file("/Roms/GB/map.txt", + "good.gb\tGood Game\n" + "bad.gb\t.Bad Game\n" + "test.gb\t.Test ROM\n"); + + char alias1[256] = ""; + char alias2[256] = ""; + char alias3[256] = ""; + + Map_getAlias("/Roms/GB/good.gb", alias1); + Map_getAlias("/Roms/GB/bad.gb", alias2); + Map_getAlias("/Roms/GB/test.gb", alias3); + + // All should get their aliases + TEST_ASSERT_EQUAL_STRING("Good Game", alias1); + TEST_ASSERT_EQUAL_STRING(".Bad Game", alias2); // Hidden (starts with .) + TEST_ASSERT_EQUAL_STRING(".Test ROM", alias3); // Hidden +} + +/////////////////////////////// +// Edge Cases +/////////////////////////////// + +void test_getAlias_path_without_directory(void) { + char alias[256] = "Default"; + Map_getAlias("mario.gb", alias); + + // No directory, can't find map.txt + TEST_ASSERT_EQUAL_STRING("Default", alias); +} + +void test_getAlias_stops_at_first_match(void) { + // Duplicate entries (should use first) + mock_fs_add_file("/Roms/map.txt", + "game.rom\tFirst Alias\n" + "game.rom\tSecond Alias\n"); + + char alias[256] = ""; + Map_getAlias("/Roms/game.rom", alias); + + TEST_ASSERT_EQUAL_STRING("First Alias", alias); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // Basic lookup + RUN_TEST(test_getAlias_finds_single_entry); + RUN_TEST(test_getAlias_finds_entry_in_multi_line_map); + RUN_TEST(test_getAlias_finds_first_entry); + RUN_TEST(test_getAlias_finds_last_entry); + + // No map / no match + RUN_TEST(test_getAlias_no_map_file_leaves_alias_unchanged); + RUN_TEST(test_getAlias_rom_not_in_map_leaves_alias_unchanged); + RUN_TEST(test_getAlias_empty_map_file_leaves_alias_unchanged); + + // Format handling + RUN_TEST(test_getAlias_skips_empty_lines); + RUN_TEST(test_getAlias_handles_lines_without_tab); + RUN_TEST(test_getAlias_exact_match_required); + RUN_TEST(test_getAlias_handles_windows_newlines); + + // Special characters + RUN_TEST(test_getAlias_with_special_characters_in_filename); + RUN_TEST(test_getAlias_with_special_characters_in_alias); + RUN_TEST(test_getAlias_hidden_rom_starts_with_dot); + + // Path tests + RUN_TEST(test_getAlias_different_directories); + RUN_TEST(test_getAlias_deep_directory_structure); + RUN_TEST(test_getAlias_looks_in_rom_directory_not_parent); + + // Integration + RUN_TEST(test_getAlias_realistic_rom_library); + RUN_TEST(test_getAlias_multiple_roms_same_directory); + RUN_TEST(test_getAlias_hidden_roms_workflow); + + // Edge cases + RUN_TEST(test_getAlias_path_without_directory); + RUN_TEST(test_getAlias_stops_at_first_match); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_minarch_paths.c b/tests/unit/all/common/test_minarch_paths.c new file mode 100644 index 00000000..35f263a0 --- /dev/null +++ b/tests/unit/all/common/test_minarch_paths.c @@ -0,0 +1,255 @@ +/** + * test_minarch_paths.c - Unit tests for MinArch path generation + * + * Tests path generation functions extracted from minarch.c. + * These are pure sprintf logic with no file system dependencies. + * + * Test coverage: + * - SRAM path generation (.sav files) + * - RTC path generation (.rtc files) + * - Save state path generation (.st0-.st9 files) + * - Config path generation (.cfg files) + */ + +#include "../../../support/unity/unity.h" +#include "../../../../workspace/all/common/minarch_paths.h" + +#include + +void setUp(void) { + // Nothing to set up +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// SRAM Path Tests +/////////////////////////////// + +void test_getSRAMPath_generates_correct_path(void) { + char path[512]; + + MinArch_getSRAMPath(path, "/mnt/SDCARD/.userdata/miyoomini/gpsp", "Pokemon Red"); + + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/.userdata/miyoomini/gpsp/Pokemon Red.sav", path); +} + +void test_getSRAMPath_handles_special_characters(void) { + char path[512]; + + MinArch_getSRAMPath(path, "/saves", "Game (USA) (Rev 1)"); + + TEST_ASSERT_EQUAL_STRING("/saves/Game (USA) (Rev 1).sav", path); +} + +void test_getSRAMPath_handles_short_names(void) { + char path[512]; + + MinArch_getSRAMPath(path, "/data", "A"); + + TEST_ASSERT_EQUAL_STRING("/data/A.sav", path); +} + +/////////////////////////////// +// RTC Path Tests +/////////////////////////////// + +void test_getRTCPath_generates_correct_path(void) { + char path[512]; + + MinArch_getRTCPath(path, "/mnt/SDCARD/.userdata/miyoomini/gpsp", "Pokemon Gold"); + + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/.userdata/miyoomini/gpsp/Pokemon Gold.rtc", path); +} + +void test_getRTCPath_different_from_SRAM(void) { + char sram_path[512]; + char rtc_path[512]; + + MinArch_getSRAMPath(sram_path, "/saves", "Game"); + MinArch_getRTCPath(rtc_path, "/saves", "Game"); + + // Should differ only in extension + TEST_ASSERT_NOT_EQUAL(0, strcmp(sram_path, rtc_path)); + TEST_ASSERT_EQUAL_STRING("/saves/Game.sav", sram_path); + TEST_ASSERT_EQUAL_STRING("/saves/Game.rtc", rtc_path); +} + +/////////////////////////////// +// Save State Path Tests +/////////////////////////////// + +void test_getStatePath_generates_path_for_slot_0(void) { + char path[512]; + + MinArch_getStatePath(path, "/states", "Super Mario", 0); + + TEST_ASSERT_EQUAL_STRING("/states/Super Mario.st0", path); +} + +void test_getStatePath_generates_path_for_slot_9(void) { + char path[512]; + + // Slot 9 is the auto-resume slot + MinArch_getStatePath(path, "/states", "Zelda", 9); + + TEST_ASSERT_EQUAL_STRING("/states/Zelda.st9", path); +} + +void test_getStatePath_all_slots_unique(void) { + char paths[10][512]; + + // Generate paths for all 10 slots + for (int i = 0; i < 10; i++) { + MinArch_getStatePath(paths[i], "/s", "Game", i); + } + + // Verify each slot has unique path + for (int i = 0; i < 10; i++) { + for (int j = i + 1; j < 10; j++) { + TEST_ASSERT_NOT_EQUAL(0, strcmp(paths[i], paths[j])); + } + } + + // Spot check + TEST_ASSERT_EQUAL_STRING("/s/Game.st0", paths[0]); + TEST_ASSERT_EQUAL_STRING("/s/Game.st5", paths[5]); + TEST_ASSERT_EQUAL_STRING("/s/Game.st9", paths[9]); +} + +void test_getStatePath_handles_long_game_names(void) { + char path[512]; + + MinArch_getStatePath(path, "/data", "The Legend of Zelda - A Link to the Past", 3); + + TEST_ASSERT_EQUAL_STRING("/data/The Legend of Zelda - A Link to the Past.st3", path); +} + +/////////////////////////////// +// Config Path Tests +/////////////////////////////// + +void test_getConfigPath_global_no_device_tag(void) { + char path[512]; + + MinArch_getConfigPath(path, "/config", NULL, NULL); + + TEST_ASSERT_EQUAL_STRING("/config/minarch.cfg", path); +} + +void test_getConfigPath_global_with_device_tag(void) { + char path[512]; + + MinArch_getConfigPath(path, "/config", NULL, "rg35xx"); + + TEST_ASSERT_EQUAL_STRING("/config/minarch-rg35xx.cfg", path); +} + +void test_getConfigPath_game_specific_no_device_tag(void) { + char path[512]; + + MinArch_getConfigPath(path, "/config", "Super Mario Bros", NULL); + + TEST_ASSERT_EQUAL_STRING("/config/Super Mario Bros.cfg", path); +} + +void test_getConfigPath_game_specific_with_device_tag(void) { + char path[512]; + + MinArch_getConfigPath(path, "/config", "Metroid", "miyoomini"); + + TEST_ASSERT_EQUAL_STRING("/config/Metroid-miyoomini.cfg", path); +} + +void test_getConfigPath_empty_device_tag_treated_as_null(void) { + char path1[512]; + char path2[512]; + + MinArch_getConfigPath(path1, "/cfg", "Game", NULL); + MinArch_getConfigPath(path2, "/cfg", "Game", ""); + + // Empty string should be treated same as NULL + TEST_ASSERT_EQUAL_STRING(path1, path2); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_all_save_files_in_same_directory(void) { + char sram_path[512]; + char rtc_path[512]; + char state_path[512]; + + const char* saves_dir = "/mnt/SDCARD/.userdata/miyoomini/gambatte"; + const char* game = "Pokemon Crystal"; + + MinArch_getSRAMPath(sram_path, saves_dir, game); + MinArch_getRTCPath(rtc_path, saves_dir, game); + MinArch_getStatePath(state_path, saves_dir, game, 0); + + // All should be in the same directory + TEST_ASSERT_TRUE(strstr(sram_path, saves_dir) == sram_path); + TEST_ASSERT_TRUE(strstr(rtc_path, saves_dir) == rtc_path); + TEST_ASSERT_TRUE(strstr(state_path, saves_dir) == state_path); + + // All should have different extensions + TEST_ASSERT_TRUE(strstr(sram_path, ".sav") != NULL); + TEST_ASSERT_TRUE(strstr(rtc_path, ".rtc") != NULL); + TEST_ASSERT_TRUE(strstr(state_path, ".st0") != NULL); +} + +void test_config_paths_distinguish_game_and_global(void) { + char game_cfg[512]; + char global_cfg[512]; + + MinArch_getConfigPath(game_cfg, "/cfg", "MyGame", NULL); + MinArch_getConfigPath(global_cfg, "/cfg", NULL, NULL); + + // Should be different paths + TEST_ASSERT_NOT_EQUAL(0, strcmp(game_cfg, global_cfg)); + + // Game config has game name + TEST_ASSERT_TRUE(strstr(game_cfg, "MyGame") != NULL); + + // Global config has "minarch" + TEST_ASSERT_TRUE(strstr(global_cfg, "minarch") != NULL); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // SRAM paths + RUN_TEST(test_getSRAMPath_generates_correct_path); + RUN_TEST(test_getSRAMPath_handles_special_characters); + RUN_TEST(test_getSRAMPath_handles_short_names); + + // RTC paths + RUN_TEST(test_getRTCPath_generates_correct_path); + RUN_TEST(test_getRTCPath_different_from_SRAM); + + // Save state paths + RUN_TEST(test_getStatePath_generates_path_for_slot_0); + RUN_TEST(test_getStatePath_generates_path_for_slot_9); + RUN_TEST(test_getStatePath_all_slots_unique); + RUN_TEST(test_getStatePath_handles_long_game_names); + + // Config paths + RUN_TEST(test_getConfigPath_global_no_device_tag); + RUN_TEST(test_getConfigPath_global_with_device_tag); + RUN_TEST(test_getConfigPath_game_specific_no_device_tag); + RUN_TEST(test_getConfigPath_game_specific_with_device_tag); + RUN_TEST(test_getConfigPath_empty_device_tag_treated_as_null); + + // Integration tests + RUN_TEST(test_all_save_files_in_same_directory); + RUN_TEST(test_config_paths_distinguish_game_and_global); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_minui_file_utils.c b/tests/unit/all/common/test_minui_file_utils.c new file mode 100644 index 00000000..c03184d0 --- /dev/null +++ b/tests/unit/all/common/test_minui_file_utils.c @@ -0,0 +1,272 @@ +/** + * test_minui_file_utils.c - Unit tests for MinUI file utilities + * + * Tests file existence checking functions extracted from minui.c. + * Uses file system mocking to test without actual files. + * + * Test coverage: + * - hasEmu() - Emulator availability checking + * - hasCue() - CUE file detection for disc games + * - hasM3u() - M3U playlist detection for multi-disc games + * + * Note: Uses GCC --wrap for file mocking (Docker-only) + */ + +#include "../../../support/unity/unity.h" +#include "../../../support/fs_mocks.h" +#include "../../../../workspace/all/common/minui_file_utils.h" + +#include + +void setUp(void) { + mock_fs_reset(); +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// hasEmu() Tests +/////////////////////////////// + +void test_hasEmu_finds_shared_emulator(void) { + // Emulator in shared Paks location + mock_fs_add_file("/mnt/SDCARD/Paks/Emus/gpsp.pak/launch.sh", "#!/bin/sh"); + + int result = MinUI_hasEmu("gpsp", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini"); + + TEST_ASSERT_TRUE(result); +} + +void test_hasEmu_finds_platform_specific_emulator(void) { + // Emulator in platform-specific location + mock_fs_add_file("/mnt/SDCARD/Emus/miyoomini/gambatte.pak/launch.sh", "#!/bin/sh"); + + int result = MinUI_hasEmu("gambatte", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini"); + + TEST_ASSERT_TRUE(result); +} + +void test_hasEmu_prefers_shared_over_platform(void) { + // Both locations have the emulator + mock_fs_add_file("/mnt/SDCARD/Paks/Emus/snes9x.pak/launch.sh", "shared"); + mock_fs_add_file("/mnt/SDCARD/Emus/miyoomini/snes9x.pak/launch.sh", "platform"); + + // Should find it (finds shared first) + int result = MinUI_hasEmu("snes9x", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini"); + + TEST_ASSERT_TRUE(result); +} + +void test_hasEmu_returns_false_when_not_found(void) { + // No emulator files exist + int result = MinUI_hasEmu("nonexistent", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini"); + + TEST_ASSERT_FALSE(result); +} + +void test_hasEmu_different_platforms(void) { + // Platform-specific emulator for rg35xx + mock_fs_add_file("/mnt/SDCARD/Emus/rg35xx/picodrive.pak/launch.sh", "#!/bin/sh"); + + // Should find for rg35xx + TEST_ASSERT_TRUE(MinUI_hasEmu("picodrive", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "rg35xx")); + + // Should NOT find for miyoomini + TEST_ASSERT_FALSE( + MinUI_hasEmu("picodrive", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini")); +} + +/////////////////////////////// +// hasCue() Tests +/////////////////////////////// + +void test_hasCue_finds_matching_cue_file(void) { + // Directory with .cue file named after directory + mock_fs_add_file("/Roms/PS1/Final Fantasy VII/Final Fantasy VII.cue", "cue data"); + + char cue_path[256]; + int result = MinUI_hasCue("/Roms/PS1/Final Fantasy VII", cue_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/Final Fantasy VII/Final Fantasy VII.cue", cue_path); +} + +void test_hasCue_returns_false_when_cue_missing(void) { + // Directory exists but no .cue file + char cue_path[256]; + int result = MinUI_hasCue("/Roms/PS1/SomeGame", cue_path); + + TEST_ASSERT_FALSE(result); + // Path should still be set (for potential creation later) + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/SomeGame/SomeGame.cue", cue_path); +} + +void test_hasCue_simple_directory_name(void) { + mock_fs_add_file("/Games/Panzer/Panzer.cue", "cue"); + + char cue_path[256]; + int result = MinUI_hasCue("/Games/Panzer", cue_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/Games/Panzer/Panzer.cue", cue_path); +} + +void test_hasCue_path_with_special_characters(void) { + mock_fs_add_file("/Roms/Game (USA) (v1.1)/Game (USA) (v1.1).cue", "cue"); + + char cue_path[256]; + int result = MinUI_hasCue("/Roms/Game (USA) (v1.1)", cue_path); + + TEST_ASSERT_TRUE(result); +} + +/////////////////////////////// +// hasM3u() Tests +/////////////////////////////// + +void test_hasM3u_finds_m3u_in_parent(void) { + // ROM in subdirectory, M3U in parent named after subdirectory + // Structure: /Roms/PS1/FF7.m3u references /Roms/PS1/FF7/disc1.bin + mock_fs_add_file("/Roms/PS1/FF7.m3u", "playlist"); + + char m3u_path[256]; + int result = MinUI_hasM3u("/Roms/PS1/FF7/disc1.bin", m3u_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/FF7.m3u", m3u_path); +} + +void test_hasM3u_returns_false_when_m3u_missing(void) { + char m3u_path[256]; + int result = MinUI_hasM3u("/Roms/PS1/Game/disc.bin", m3u_path); + + TEST_ASSERT_FALSE(result); + // Path should still be constructed + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/Game.m3u", m3u_path); +} + +void test_hasM3u_deep_directory_structure(void) { + mock_fs_add_file("/a/b/c/Game.m3u", "playlist"); + + char m3u_path[256]; + int result = MinUI_hasM3u("/a/b/c/Game/disc1.bin", m3u_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/a/b/c/Game.m3u", m3u_path); +} + +void test_hasM3u_realistic_final_fantasy(void) { + // Real-world scenario + mock_fs_add_file("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA).m3u", "playlist"); + + char m3u_path[256]; + int result = + MinUI_hasM3u("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA)/FF7 (Disc 1).bin", m3u_path); + + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_STRING("/mnt/SDCARD/Roms/PS1/Final Fantasy VII (USA).m3u", m3u_path); +} + +void test_hasM3u_constructs_correct_path_even_when_missing(void) { + char m3u_path[256]; + MinUI_hasM3u("/Roms/Saturn/Panzer/disc.cue", m3u_path); + + // Even if file doesn't exist, path should be constructed correctly + TEST_ASSERT_EQUAL_STRING("/Roms/Saturn/Panzer.m3u", m3u_path); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_multi_disc_game_workflow(void) { + // Complete multi-disc game setup + const char* game_dir = "/Roms/PS1/Metal Gear Solid"; + const char* disc1 = "/Roms/PS1/Metal Gear Solid/MGS (Disc 1).bin"; + + // Add M3U playlist + mock_fs_add_file("/Roms/PS1/Metal Gear Solid.m3u", "MGS (Disc 1).bin\nMGS (Disc 2).bin\n"); + + // Check for M3U + char m3u_path[256]; + TEST_ASSERT_TRUE(MinUI_hasM3u(disc1, m3u_path)); + TEST_ASSERT_EQUAL_STRING("/Roms/PS1/Metal Gear Solid.m3u", m3u_path); + + // Also check for CUE (game might have both) + mock_fs_add_file("/Roms/PS1/Metal Gear Solid/Metal Gear Solid.cue", "cue"); + char cue_path[256]; + TEST_ASSERT_TRUE(MinUI_hasCue(game_dir, cue_path)); +} + +void test_emulator_availability_check(void) { + // Setup emulators for different systems + mock_fs_add_file("/mnt/SDCARD/Paks/Emus/gpsp.pak/launch.sh", "gba"); + mock_fs_add_file("/mnt/SDCARD/Emus/miyoomini/gambatte.pak/launch.sh", "gb"); + + // Check which emulators are available + TEST_ASSERT_TRUE(MinUI_hasEmu("gpsp", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini")); + TEST_ASSERT_TRUE( + MinUI_hasEmu("gambatte", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini")); + TEST_ASSERT_FALSE( + MinUI_hasEmu("nonexistent", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini")); +} + +/////////////////////////////// +// Edge Cases +/////////////////////////////// + +void test_hasCue_path_without_slash(void) { + char cue_path[256]; + int result = MinUI_hasCue("NoSlashes", cue_path); + + // Should handle gracefully + TEST_ASSERT_FALSE(result); +} + +void test_hasM3u_shallow_path(void) { + char m3u_path[256]; + int result = MinUI_hasM3u("/Game/disc.bin", m3u_path); + + // Path too shallow for M3U detection + TEST_ASSERT_FALSE(result); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // hasEmu tests + RUN_TEST(test_hasEmu_finds_shared_emulator); + RUN_TEST(test_hasEmu_finds_platform_specific_emulator); + RUN_TEST(test_hasEmu_prefers_shared_over_platform); + RUN_TEST(test_hasEmu_returns_false_when_not_found); + RUN_TEST(test_hasEmu_different_platforms); + + // hasCue tests + RUN_TEST(test_hasCue_finds_matching_cue_file); + RUN_TEST(test_hasCue_returns_false_when_cue_missing); + RUN_TEST(test_hasCue_simple_directory_name); + RUN_TEST(test_hasCue_path_with_special_characters); + + // hasM3u tests + RUN_TEST(test_hasM3u_finds_m3u_in_parent); + RUN_TEST(test_hasM3u_returns_false_when_m3u_missing); + RUN_TEST(test_hasM3u_deep_directory_structure); + RUN_TEST(test_hasM3u_realistic_final_fantasy); + RUN_TEST(test_hasM3u_constructs_correct_path_even_when_missing); + + // Integration tests + RUN_TEST(test_multi_disc_game_workflow); + RUN_TEST(test_emulator_availability_check); + + // Edge cases + RUN_TEST(test_hasCue_path_without_slash); + RUN_TEST(test_hasM3u_shallow_path); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_minui_utils.c b/tests/unit/all/common/test_minui_utils.c new file mode 100644 index 00000000..0f012336 --- /dev/null +++ b/tests/unit/all/common/test_minui_utils.c @@ -0,0 +1,184 @@ +/** + * test_minui_utils.c - Unit tests for MinUI launcher utilities + * + * Tests helper functions extracted from minui.c. + * These are pure string logic with no file system dependencies. + * + * Test coverage: + * - MinUI_getIndexChar() - Alphabetical index for quick navigation + * - MinUI_isConsoleDir() - Console directory classification + */ + +#include "../../../support/unity/unity.h" +#include "../../../../workspace/all/common/minui_utils.h" + +void setUp(void) { + // Nothing to set up +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// getIndexChar() Tests +/////////////////////////////// + +void test_getIndexChar_lowercase_letters(void) { + TEST_ASSERT_EQUAL_INT(1, MinUI_getIndexChar("apple")); + TEST_ASSERT_EQUAL_INT(2, MinUI_getIndexChar("banana")); + TEST_ASSERT_EQUAL_INT(13, MinUI_getIndexChar("mario")); + TEST_ASSERT_EQUAL_INT(26, MinUI_getIndexChar("zelda")); +} + +void test_getIndexChar_uppercase_letters(void) { + TEST_ASSERT_EQUAL_INT(1, MinUI_getIndexChar("Apple")); + TEST_ASSERT_EQUAL_INT(2, MinUI_getIndexChar("BANANA")); + TEST_ASSERT_EQUAL_INT(13, MinUI_getIndexChar("Mario")); + TEST_ASSERT_EQUAL_INT(26, MinUI_getIndexChar("ZELDA")); +} + +void test_getIndexChar_mixed_case(void) { + TEST_ASSERT_EQUAL_INT(1, MinUI_getIndexChar("ApPlE")); + TEST_ASSERT_EQUAL_INT(26, MinUI_getIndexChar("zElDa")); +} + +void test_getIndexChar_numbers_return_zero(void) { + TEST_ASSERT_EQUAL_INT(0, MinUI_getIndexChar("007 GoldenEye")); + TEST_ASSERT_EQUAL_INT(0, MinUI_getIndexChar("1942")); + TEST_ASSERT_EQUAL_INT(0, MinUI_getIndexChar("3D Worldrunner")); +} + +void test_getIndexChar_special_chars_return_zero(void) { + TEST_ASSERT_EQUAL_INT(0, MinUI_getIndexChar("!Exclaim")); + TEST_ASSERT_EQUAL_INT(0, MinUI_getIndexChar("@Home")); + TEST_ASSERT_EQUAL_INT(0, MinUI_getIndexChar(".hidden")); +} + +void test_getIndexChar_empty_string_returns_zero(void) { + TEST_ASSERT_EQUAL_INT(0, MinUI_getIndexChar("")); +} + +void test_getIndexChar_single_letter(void) { + TEST_ASSERT_EQUAL_INT(1, MinUI_getIndexChar("A")); + TEST_ASSERT_EQUAL_INT(26, MinUI_getIndexChar("z")); +} + +/////////////////////////////// +// isConsoleDir() Tests +/////////////////////////////// + +void test_isConsoleDir_top_level_console_directory(void) { + // /mnt/SDCARD/Roms/GB has parent /mnt/SDCARD/Roms + int result = MinUI_isConsoleDir("/mnt/SDCARD/Roms/GB", "/mnt/SDCARD/Roms"); + + TEST_ASSERT_TRUE(result); +} + +void test_isConsoleDir_another_console(void) { + int result = MinUI_isConsoleDir("/mnt/SDCARD/Roms/NES", "/mnt/SDCARD/Roms"); + + TEST_ASSERT_TRUE(result); +} + +void test_isConsoleDir_subdirectory_not_console(void) { + // /mnt/SDCARD/Roms/GB/Homebrew has parent /mnt/SDCARD/Roms/GB (not Roms) + int result = MinUI_isConsoleDir("/mnt/SDCARD/Roms/GB/Homebrew", "/mnt/SDCARD/Roms"); + + TEST_ASSERT_FALSE(result); +} + +void test_isConsoleDir_deep_subdirectory_not_console(void) { + int result = + MinUI_isConsoleDir("/mnt/SDCARD/Roms/GB/Collections/Action", "/mnt/SDCARD/Roms"); + + TEST_ASSERT_FALSE(result); +} + +void test_isConsoleDir_non_roms_directory(void) { + int result = MinUI_isConsoleDir("/mnt/SDCARD/Tools/say.pak", "/mnt/SDCARD/Roms"); + + TEST_ASSERT_FALSE(result); +} + +void test_isConsoleDir_roms_path_itself_not_console(void) { + // The Roms directory itself is not a console directory + int result = MinUI_isConsoleDir("/mnt/SDCARD/Roms", "/mnt/SDCARD/Roms"); + + TEST_ASSERT_FALSE(result); +} + +void test_isConsoleDir_different_roms_path(void) { + // Test with different ROMS_PATH + int result = MinUI_isConsoleDir("/sd/Roms/SNES", "/sd/Roms"); + + TEST_ASSERT_TRUE(result); +} + +void test_isConsoleDir_path_without_slash_returns_false(void) { + // Edge case: path with no slashes + int result = MinUI_isConsoleDir("GB", "/mnt/SDCARD/Roms"); + + TEST_ASSERT_FALSE(result); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_index_char_for_rom_library(void) { + // Simulate indexing a ROM library + char* roms[] = {"Advance Wars", "Metroid", "Pokemon", "Zelda", "007 Racing", NULL}; + int expected[] = {1, 13, 16, 26, 0}; + + for (int i = 0; roms[i] != NULL; i++) { + TEST_ASSERT_EQUAL_INT(expected[i], MinUI_getIndexChar(roms[i])); + } +} + +void test_console_dir_classification(void) { + const char* roms = "/mnt/SDCARD/Roms"; + + // Console directories (direct children of Roms/) + TEST_ASSERT_TRUE(MinUI_isConsoleDir("/mnt/SDCARD/Roms/GB", roms)); + TEST_ASSERT_TRUE(MinUI_isConsoleDir("/mnt/SDCARD/Roms/GBA", roms)); + TEST_ASSERT_TRUE(MinUI_isConsoleDir("/mnt/SDCARD/Roms/NES", roms)); + + // Not console directories + TEST_ASSERT_FALSE(MinUI_isConsoleDir("/mnt/SDCARD/Roms/GB/Action", roms)); + TEST_ASSERT_FALSE(MinUI_isConsoleDir("/mnt/SDCARD/Tools", roms)); + TEST_ASSERT_FALSE(MinUI_isConsoleDir("/mnt/SDCARD/Roms", roms)); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // getIndexChar tests + RUN_TEST(test_getIndexChar_lowercase_letters); + RUN_TEST(test_getIndexChar_uppercase_letters); + RUN_TEST(test_getIndexChar_mixed_case); + RUN_TEST(test_getIndexChar_numbers_return_zero); + RUN_TEST(test_getIndexChar_special_chars_return_zero); + RUN_TEST(test_getIndexChar_empty_string_returns_zero); + RUN_TEST(test_getIndexChar_single_letter); + + // isConsoleDir tests + RUN_TEST(test_isConsoleDir_top_level_console_directory); + RUN_TEST(test_isConsoleDir_another_console); + RUN_TEST(test_isConsoleDir_subdirectory_not_console); + RUN_TEST(test_isConsoleDir_deep_subdirectory_not_console); + RUN_TEST(test_isConsoleDir_non_roms_directory); + RUN_TEST(test_isConsoleDir_roms_path_itself_not_console); + RUN_TEST(test_isConsoleDir_different_roms_path); + RUN_TEST(test_isConsoleDir_path_without_slash_returns_false); + + // Integration tests + RUN_TEST(test_index_char_for_rom_library); + RUN_TEST(test_console_dir_classification); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_recent_parser.c b/tests/unit/all/common/test_recent_parser.c new file mode 100644 index 00000000..2eebeee1 --- /dev/null +++ b/tests/unit/all/common/test_recent_parser.c @@ -0,0 +1,305 @@ +/** + * test_recent_parser.c - Unit tests for recent.txt parser + * + * Tests recently played games file parsing extracted from minui.c. + * Recent.txt uses tab-delimited format: pathalias + * + * Test coverage: + * - Basic parsing (with and without aliases) + * - Tab-delimited format handling + * - Missing ROM filtering + * - Empty line handling + * - Order preservation + * + * Note: Uses GCC --wrap for file mocking (Docker-only) + * For write tests, see test_recent_writer.c (uses real temp files) + */ + +#include "../../../support/unity/unity.h" +#include "../../../support/fs_mocks.h" +#include "../../../../workspace/all/common/recent_file.h" + +#include + +void setUp(void) { + mock_fs_reset(); +} + +void tearDown(void) { + // Nothing to clean up +} + +/////////////////////////////// +// Basic Parsing Tests +/////////////////////////////// + +void test_Recent_parse_single_entry_no_alias(void) { + mock_fs_add_file("/recent.txt", "/Roms/GB/mario.gb\n"); + mock_fs_add_file("/mnt/SDCARD/Roms/GB/mario.gb", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(1, count); + TEST_ASSERT_EQUAL_STRING("/Roms/GB/mario.gb", entries[0]->path); + TEST_ASSERT_NULL(entries[0]->alias); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_single_entry_with_alias(void) { + mock_fs_add_file("/recent.txt", "/Roms/GB/mario.gb\tSuper Mario Land\n"); + mock_fs_add_file("/mnt/SDCARD/Roms/GB/mario.gb", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(1, count); + TEST_ASSERT_EQUAL_STRING("/Roms/GB/mario.gb", entries[0]->path); + TEST_ASSERT_EQUAL_STRING("Super Mario Land", entries[0]->alias); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_multiple_entries_mixed(void) { + mock_fs_add_file("/recent.txt", + "/Roms/GB/mario.gb\tSuper Mario\n" + "/Roms/NES/zelda.nes\n" + "/Roms/SNES/metroid.smc\tSuper Metroid\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/GB/mario.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/NES/zelda.nes", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/SNES/metroid.smc", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(3, count); + + // First entry: has alias + TEST_ASSERT_EQUAL_STRING("/Roms/GB/mario.gb", entries[0]->path); + TEST_ASSERT_EQUAL_STRING("Super Mario", entries[0]->alias); + + // Second entry: no alias + TEST_ASSERT_EQUAL_STRING("/Roms/NES/zelda.nes", entries[1]->path); + TEST_ASSERT_NULL(entries[1]->alias); + + // Third entry: has alias + TEST_ASSERT_EQUAL_STRING("/Roms/SNES/metroid.smc", entries[2]->path); + TEST_ASSERT_EQUAL_STRING("Super Metroid", entries[2]->alias); + + Recent_freeEntries(entries, count); +} + +/////////////////////////////// +// Filtering Tests +/////////////////////////////// + +void test_Recent_parse_skips_missing_roms(void) { + mock_fs_add_file("/recent.txt", + "/Roms/exists.gb\n" + "/Roms/missing.gb\n" + "/Roms/also_exists.nes\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/exists.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/also_exists.nes", "rom"); + // Not creating missing.gb + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + // Should only include the 2 that exist + TEST_ASSERT_EQUAL_INT(2, count); + TEST_ASSERT_EQUAL_STRING("/Roms/exists.gb", entries[0]->path); + TEST_ASSERT_EQUAL_STRING("/Roms/also_exists.nes", entries[1]->path); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_skips_empty_lines(void) { + mock_fs_add_file("/recent.txt", + "\n" + "/Roms/game1.gb\n" + "\n" + "/Roms/game2.gb\n" + "\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/game1.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/game2.gb", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(2, count); + + Recent_freeEntries(entries, count); +} + +/////////////////////////////// +// Format Tests +/////////////////////////////// + +void test_Recent_parse_handles_windows_newlines(void) { + mock_fs_add_file("/recent.txt", "/Roms/game.gb\tAlias\r\n"); + mock_fs_add_file("/mnt/SDCARD/Roms/game.gb", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(1, count); + TEST_ASSERT_EQUAL_STRING("Alias", entries[0]->alias); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_alias_with_special_characters(void) { + mock_fs_add_file("/recent.txt", "/Roms/game.gb\tGame™ - The Best! (v1.1)\n"); + mock_fs_add_file("/mnt/SDCARD/Roms/game.gb", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_STRING("Game™ - The Best! (v1.1)", entries[0]->alias); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_entry_without_tab_has_no_alias(void) { + mock_fs_add_file("/recent.txt", "/Roms/game.gb\n"); + mock_fs_add_file("/mnt/SDCARD/Roms/game.gb", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_NULL(entries[0]->alias); + + Recent_freeEntries(entries, count); +} + +/////////////////////////////// +// Error Cases +/////////////////////////////// + +void test_Recent_parse_file_not_found_returns_zero(void) { + int count; + Recent_Entry** entries = Recent_parse("/nonexistent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_empty_file_returns_zero(void) { + mock_fs_add_file("/recent.txt", ""); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_all_roms_missing_returns_zero(void) { + mock_fs_add_file("/recent.txt", + "/Roms/missing1.gb\n" + "/Roms/missing2.nes\n"); + + // Don't create any ROM files + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(0, count); + + Recent_freeEntries(entries, count); +} + +/////////////////////////////// +// Integration Tests +/////////////////////////////// + +void test_Recent_parse_realistic_recent_list(void) { + mock_fs_add_file("/mnt/SDCARD/.minui/recent.txt", + "/Roms/PS1/Final Fantasy VII/FF7 (Disc 1).bin\tFF7\n" + "/Roms/GB/Pokemon - Red Version (USA).gb\tPokemon Red\n" + "/Roms/NES/Super Mario Bros (World).nes\n" + "/Roms/SNES/Super Metroid (USA).smc\tMetroid\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/PS1/Final Fantasy VII/FF7 (Disc 1).bin", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/GB/Pokemon - Red Version (USA).gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/NES/Super Mario Bros (World).nes", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/SNES/Super Metroid (USA).smc", "rom"); + + int count; + Recent_Entry** entries = + Recent_parse("/mnt/SDCARD/.minui/recent.txt", "/mnt/SDCARD", &count); + + TEST_ASSERT_EQUAL_INT(4, count); + + // Verify paths and aliases + TEST_ASSERT_TRUE(strstr(entries[0]->path, "FF7") != NULL); + TEST_ASSERT_EQUAL_STRING("FF7", entries[0]->alias); + + TEST_ASSERT_TRUE(strstr(entries[1]->path, "Pokemon") != NULL); + TEST_ASSERT_EQUAL_STRING("Pokemon Red", entries[1]->alias); + + TEST_ASSERT_TRUE(strstr(entries[2]->path, "Mario") != NULL); + TEST_ASSERT_NULL(entries[2]->alias); + + Recent_freeEntries(entries, count); +} + +void test_Recent_parse_maintains_order(void) { + // Recent list is ordered newest first + mock_fs_add_file("/recent.txt", + "/Roms/newest.gb\n" + "/Roms/middle.gb\n" + "/Roms/oldest.gb\n"); + + mock_fs_add_file("/mnt/SDCARD/Roms/newest.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/middle.gb", "rom"); + mock_fs_add_file("/mnt/SDCARD/Roms/oldest.gb", "rom"); + + int count; + Recent_Entry** entries = Recent_parse("/recent.txt", "/mnt/SDCARD", &count); + + // Order should match file order (newest first) + TEST_ASSERT_EQUAL_STRING("/Roms/newest.gb", entries[0]->path); + TEST_ASSERT_EQUAL_STRING("/Roms/middle.gb", entries[1]->path); + TEST_ASSERT_EQUAL_STRING("/Roms/oldest.gb", entries[2]->path); + + Recent_freeEntries(entries, count); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // Basic parsing + RUN_TEST(test_Recent_parse_single_entry_no_alias); + RUN_TEST(test_Recent_parse_single_entry_with_alias); + RUN_TEST(test_Recent_parse_multiple_entries_mixed); + + // Filtering + RUN_TEST(test_Recent_parse_skips_missing_roms); + RUN_TEST(test_Recent_parse_skips_empty_lines); + + // Format handling + RUN_TEST(test_Recent_parse_handles_windows_newlines); + RUN_TEST(test_Recent_parse_alias_with_special_characters); + RUN_TEST(test_Recent_parse_entry_without_tab_has_no_alias); + + // Error cases + RUN_TEST(test_Recent_parse_file_not_found_returns_zero); + RUN_TEST(test_Recent_parse_empty_file_returns_zero); + RUN_TEST(test_Recent_parse_all_roms_missing_returns_zero); + + // Integration + RUN_TEST(test_Recent_parse_realistic_recent_list); + RUN_TEST(test_Recent_parse_maintains_order); + + return UNITY_END(); +} diff --git a/tests/unit/all/common/test_recent_writer.c b/tests/unit/all/common/test_recent_writer.c new file mode 100644 index 00000000..23d27776 --- /dev/null +++ b/tests/unit/all/common/test_recent_writer.c @@ -0,0 +1,145 @@ +/** + * test_recent_writer.c - Tests for Recent_save() function + * + * Tests writing recent.txt files using real temp files. + * Compiled WITHOUT --wrap flags to use real file I/O. + */ + +#include "../../../support/unity/unity.h" +#include "../../../../workspace/all/common/recent_file.h" + +#include +#include +#include +#include + +void setUp(void) { + // Nothing to set up +} + +void tearDown(void) { + // Nothing to clean up +} + +void test_Recent_save_single_entry_no_alias(void) { + // Create temp file + char temp_path[] = "/tmp/recent_save_test_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + // Create entry + Recent_Entry entry1 = {.path = "/Roms/GB/mario.gb", .alias = NULL}; + Recent_Entry* entries[] = {&entry1}; + + // Save + int result = Recent_save(temp_path, entries, 1); + TEST_ASSERT_TRUE(result); + + // Read back and verify + FILE* f = fopen(temp_path, "r"); + TEST_ASSERT_NOT_NULL(f); + char line[256]; + fgets(line, sizeof(line), f); + fclose(f); + + TEST_ASSERT_EQUAL_STRING("/Roms/GB/mario.gb\n", line); + + // Cleanup + unlink(temp_path); +} + +void test_Recent_save_single_entry_with_alias(void) { + char temp_path[] = "/tmp/recent_save_test_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + Recent_Entry entry1 = {.path = "/Roms/GB/mario.gb", .alias = "Super Mario"}; + Recent_Entry* entries[] = {&entry1}; + + int result = Recent_save(temp_path, entries, 1); + TEST_ASSERT_TRUE(result); + + FILE* f = fopen(temp_path, "r"); + TEST_ASSERT_NOT_NULL(f); + char line[256]; + fgets(line, sizeof(line), f); + fclose(f); + + TEST_ASSERT_EQUAL_STRING("/Roms/GB/mario.gb\tSuper Mario\n", line); + + unlink(temp_path); +} + +void test_Recent_save_multiple_entries_mixed(void) { + char temp_path[] = "/tmp/recent_save_test_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + Recent_Entry entry1 = {.path = "/Roms/GB/mario.gb", .alias = "Super Mario"}; + Recent_Entry entry2 = {.path = "/Roms/NES/zelda.nes", .alias = NULL}; + Recent_Entry entry3 = {.path = "/Roms/SNES/metroid.smc", .alias = "Metroid"}; + Recent_Entry* entries[] = {&entry1, &entry2, &entry3}; + + int result = Recent_save(temp_path, entries, 3); + TEST_ASSERT_TRUE(result); + + FILE* f = fopen(temp_path, "r"); + TEST_ASSERT_NOT_NULL(f); + char line1[256], line2[256], line3[256]; + fgets(line1, sizeof(line1), f); + fgets(line2, sizeof(line2), f); + fgets(line3, sizeof(line3), f); + fclose(f); + + TEST_ASSERT_EQUAL_STRING("/Roms/GB/mario.gb\tSuper Mario\n", line1); + TEST_ASSERT_EQUAL_STRING("/Roms/NES/zelda.nes\n", line2); + TEST_ASSERT_EQUAL_STRING("/Roms/SNES/metroid.smc\tMetroid\n", line3); + + unlink(temp_path); +} + +void test_Recent_save_empty_array(void) { + char temp_path[] = "/tmp/recent_save_test_XXXXXX"; + int fd = mkstemp(temp_path); + TEST_ASSERT_TRUE(fd >= 0); + close(fd); + + // Save empty array + int result = Recent_save(temp_path, NULL, 0); + TEST_ASSERT_TRUE(result); + + // File should exist but be empty + FILE* f = fopen(temp_path, "r"); + TEST_ASSERT_NOT_NULL(f); + char line[256]; + char* res = fgets(line, sizeof(line), f); + fclose(f); + + TEST_ASSERT_NULL(res); // Empty file + + unlink(temp_path); +} + +void test_Recent_save_file_open_failure(void) { + // Try to save to invalid path + Recent_Entry entry1 = {.path = "/Roms/GB/mario.gb", .alias = NULL}; + Recent_Entry* entries[] = {&entry1}; + + int result = Recent_save("/nonexistent/path/recent.txt", entries, 1); + TEST_ASSERT_FALSE(result); +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_Recent_save_single_entry_no_alias); + RUN_TEST(test_Recent_save_single_entry_with_alias); + RUN_TEST(test_Recent_save_multiple_entries_mixed); + RUN_TEST(test_Recent_save_empty_array); + RUN_TEST(test_Recent_save_file_open_failure); + + return UNITY_END(); +} diff --git a/workspace/all/clock/makefile b/workspace/all/clock/makefile index c92cc1d6..cf134623 100644 --- a/workspace/all/clock/makefile +++ b/workspace/all/clock/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = clock INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/collections.c ../common/pad.c ../common/gfx_text.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS = $(ARCH) -fomit-frame-pointer diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index d56137fa..63b8613f 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -39,6 +39,8 @@ #include "api.h" #include "defines.h" +#include "gfx_text.h" +#include "pad.h" #include "utils.h" /////////////////////////////// @@ -402,98 +404,9 @@ FALLBACK_IMPLEMENTATION void PLAT_setEffectColor(int next_color) {} * @param padding Additional padding to account for in width * @return Final width of the text in pixels (including padding) */ -int GFX_truncateText(TTF_Font* font, const char* in_name, char* out_name, int max_width, - int padding) { - int text_width; - strcpy(out_name, in_name); - TTF_SizeUTF8(font, out_name, &text_width, NULL); - text_width += padding; - - while (text_width > max_width) { - int len = strlen(out_name); - strcpy(&out_name[len - 4], "...\0"); - TTF_SizeUTF8(font, out_name, &text_width, NULL); - text_width += padding; - } - - return text_width; -} - -/** - * Wraps text to fit within a maximum width by inserting newlines. - * - * Breaks text at space characters to create wrapped lines. The last - * line is truncated with "..." if it still exceeds max_width. - * Modifies the input string in place by replacing spaces with newlines. - * - * @param font TTF font to measure text with - * @param str String to wrap (modified in place) - * @param max_width Maximum width per line in pixels - * @param max_lines Maximum number of lines (0 for unlimited) - * @return Width of the widest line in pixels - * - * @note Input string is modified - spaces become newlines at wrap points - */ -int GFX_wrapText(TTF_Font* font, char* str, int max_width, int max_lines) { - if (!str) - return 0; - - int line_width; - int max_line_width = 0; - char* line = str; - char buffer[MAX_PATH]; - - TTF_SizeUTF8(font, line, &line_width, NULL); - if (line_width <= max_width) { - line_width = GFX_truncateText(font, line, buffer, max_width, 0); - strcpy(line, buffer); - return line_width; - } +// Note: GFX_truncateText() moved to workspace/all/common/gfx_text.c for testability - char* prev = NULL; - char* tmp = line; - int lines = 1; - while (!max_lines || lines < max_lines) { - tmp = strchr(tmp, ' '); - if (!tmp) { - if (prev) { - TTF_SizeUTF8(font, line, &line_width, NULL); - if (line_width >= max_width) { - if (line_width > max_line_width) - max_line_width = line_width; - prev[0] = '\n'; - line = prev + 1; - } - } - break; - } - tmp[0] = '\0'; - - TTF_SizeUTF8(font, line, &line_width, NULL); - - if (line_width >= max_width) { // wrap - if (line_width > max_line_width) - max_line_width = line_width; - tmp[0] = ' '; - tmp += 1; - prev[0] = '\n'; - prev += 1; - line = prev; - lines += 1; - } else { // continue - tmp[0] = ' '; - prev = tmp; - tmp += 1; - } - } - - line_width = GFX_truncateText(font, line, buffer, max_width, 0); - strcpy(line, buffer); - - if (line_width > max_line_width) - max_line_width = line_width; - return max_line_width; -} +// Note: GFX_wrapText() moved to workspace/all/common/gfx_text.c for testability /////////////////////////////// // Graphics - Anti-aliased scaling (from picoarch) @@ -1181,34 +1094,7 @@ int GFX_blitButtonGroup(char** pairs, int primary, SDL_Surface* dst, int align_r * @param w Output: width of widest line * @param h Output: total height (lines * leading) */ -void GFX_sizeText(TTF_Font* font, char* str, int leading, int* w, int* h) { - char* lines[MAX_TEXT_LINES]; - int count = splitTextLines(str, lines, MAX_TEXT_LINES); - *h = count * leading; - - int mw = 0; - char line[256]; - for (int i = 0; i < count; i++) { - int len; - if (i + 1 < count) { - len = lines[i + 1] - lines[i] - 1; - if (len) - strncpy(line, lines[i], len); - line[len] = '\0'; - } else { - len = strlen(lines[i]); - strcpy(line, lines[i]); - } - - if (len) { - int lw; - TTF_SizeUTF8(font, line, &lw, NULL); - if (lw > mw) - mw = lw; - } - } - *w = mw; -} +// Note: GFX_sizeText() moved to workspace/all/common/gfx_text.c for testability /** * Renders multi-line text centered in a rectangular area. @@ -1591,82 +1477,8 @@ FALLBACK_IMPLEMENTATION int PLAT_lidChanged(int* state) { // Input - Button and analog stick handling /////////////////////////////// -// Global input state, polled each frame -PAD_Context pad; - -// Analog stick deadzone (threshold for registering input) -#define AXIS_DEADZONE 0x4000 - -/** - * Processes analog stick movement and updates button state. - * - * Converts analog axis value to digital button presses (up/down/left/right). - * Handles deadzone, button repeat, and opposite direction cancellation. - * - * @param neg_id Button ID for negative direction (left/up) - * @param pos_id Button ID for positive direction (right/down) - * @param value Analog axis value (-32768 to 32767) - * @param repeat_at Timestamp when button should start repeating - * - * @note Called internally by PLAT_pollInput for analog stick axes - */ -void PAD_setAnalog(int neg_id, int pos_id, int value, int repeat_at) { - // LOG_info("neg %i pos %i value %i\n", neg_id, pos_id, value); - int neg = 1 << neg_id; - int pos = 1 << pos_id; - if (value > AXIS_DEADZONE) { // pressing - if (!(pad.is_pressed & pos)) { // not pressing - pad.is_pressed |= pos; // set - pad.just_pressed |= pos; // set - pad.just_repeated |= pos; // set - pad.repeat_at[pos_id] = repeat_at; - - if (pad.is_pressed & neg) { // was pressing opposite - pad.is_pressed &= ~neg; // unset - pad.just_repeated &= ~neg; // unset - pad.just_released |= neg; // set - } - } - } else if (value < -AXIS_DEADZONE) { // pressing - if (!(pad.is_pressed & neg)) { // not pressing - pad.is_pressed |= neg; // set - pad.just_pressed |= neg; // set - pad.just_repeated |= neg; // set - pad.repeat_at[neg_id] = repeat_at; - - if (pad.is_pressed & pos) { // was pressing opposite - pad.is_pressed &= ~pos; // unset - pad.just_repeated &= ~pos; // unset - pad.just_released |= pos; // set - } - } - } else { // not pressing - if (pad.is_pressed & neg) { // was pressing - pad.is_pressed &= ~neg; // unset - pad.just_repeated &= neg; // unset - pad.just_released |= neg; // set - } - if (pad.is_pressed & pos) { // was pressing - pad.is_pressed &= ~pos; // unset - pad.just_repeated &= pos; // unset - pad.just_released |= pos; // set - } - } -} - -/** - * Resets all button states to unpressed. - * - * Clears all button press/release/repeat flags. - * Call this when changing contexts (e.g., entering/exiting sleep). - */ -void PAD_reset(void) { - // LOG_info("PAD_reset"); - pad.just_pressed = BTN_NONE; - pad.is_pressed = BTN_NONE; - pad.just_released = BTN_NONE; - pad.just_repeated = BTN_NONE; -} +// Note: PAD_* logic functions are now in pad.c for better testability. +// This file only contains the SDL-dependent input polling (PLAT_pollInput). /** * Polls input devices and updates global button state. @@ -2029,98 +1841,6 @@ FALLBACK_IMPLEMENTATION int PLAT_shouldWake(void) { return 0; } -/** - * Checks if any button was just pressed this frame. - * - * @return 1 if any button was just pressed, 0 otherwise - */ -int PAD_anyJustPressed(void) { - return pad.just_pressed != BTN_NONE; -} - -/** - * Checks if any button is currently held down. - * - * @return 1 if any button is pressed, 0 otherwise - */ -int PAD_anyPressed(void) { - return pad.is_pressed != BTN_NONE; -} - -/** - * Checks if any button was just released this frame. - * - * @return 1 if any button was just released, 0 otherwise - */ -int PAD_anyJustReleased(void) { - return pad.just_released != BTN_NONE; -} - -/** - * Checks if a specific button was just pressed this frame. - * - * @param btn Button bitmask (e.g., BTN_A, BTN_START) - * @return 1 if button was just pressed, 0 otherwise - */ -int PAD_justPressed(int btn) { - return pad.just_pressed & btn; -} - -/** - * Checks if a specific button is currently held down. - * - * @param btn Button bitmask (e.g., BTN_A, BTN_START) - * @return 1 if button is pressed, 0 otherwise - */ -int PAD_isPressed(int btn) { - return pad.is_pressed & btn; -} - -/** - * Checks if a specific button was just released this frame. - * - * @param btn Button bitmask (e.g., BTN_A, BTN_START) - * @return 1 if button was just released, 0 otherwise - */ -int PAD_justReleased(int btn) { - return pad.just_released & btn; -} - -/** - * Checks if a specific button is repeating (held for repeat interval). - * - * @param btn Button bitmask (e.g., BTN_DPAD_UP) - * @return 1 if button is repeating this frame, 0 otherwise - */ -int PAD_justRepeated(int btn) { - return pad.just_repeated & btn; -} - -/** - * Detects a quick tap of the menu button. - * - * Returns true if menu button was pressed and released within MENU_DELAY (250ms) - * without any brightness adjustment (PLUS/MINUS) being triggered. - * - * @param now Current timestamp in milliseconds - * @return 1 if menu was tapped (not held), 0 otherwise - * - * @note Used to distinguish menu tap from menu+brightness adjustment - */ -int PAD_tappedMenu(uint32_t now) { -#define MENU_DELAY 250 // also in PWR_update() - static uint32_t menu_start = 0; - static int ignore_menu = 0; - if (PAD_justPressed(BTN_MENU)) { - ignore_menu = 0; - menu_start = now; - } else if (PAD_isPressed(BTN_MENU) && BTN_MOD_BRIGHTNESS == BTN_MENU && - (PAD_justPressed(BTN_MOD_PLUS) || PAD_justPressed(BTN_MOD_MINUS))) { - ignore_menu = 1; - } - return (!ignore_menu && PAD_justReleased(BTN_MENU) && now - menu_start < MENU_DELAY); -} - /////////////////////////////// // Vibration - Rumble motor control /////////////////////////////// diff --git a/workspace/all/common/audio_resampler.c b/workspace/all/common/audio_resampler.c new file mode 100644 index 00000000..dc5450e4 --- /dev/null +++ b/workspace/all/common/audio_resampler.c @@ -0,0 +1,89 @@ +/** + * audio_resampler.c - Sample rate conversion for audio + * + * Implements nearest-neighbor resampling using a Bresenham-like algorithm. + * Extracted from api.c for testability. + */ + +#include "audio_resampler.h" + +/** + * Initializes a resampler for given sample rates. + * + * @param resampler Resampler instance to initialize + * @param rate_in Input sample rate in Hz + * @param rate_out Output sample rate in Hz + */ +void AudioResampler_init(AudioResampler* resampler, int rate_in, int rate_out) { + resampler->sample_rate_in = rate_in; + resampler->sample_rate_out = rate_out; + resampler->diff = 0; +} + +/** + * Resets the resampler's internal state. + * + * @param resampler Resampler instance to reset + */ +void AudioResampler_reset(AudioResampler* resampler) { + resampler->diff = 0; +} + +/** + * Processes a single audio frame through nearest-neighbor resampling. + * + * Uses Bresenham-like algorithm to determine when to write frames to the + * ring buffer and when to advance the input. + * + * Algorithm: + * 1. If diff < sample_rate_out: Write frame to buffer, add sample_rate_in to diff + * 2. If diff >= sample_rate_out: Consumed=1, subtract sample_rate_out from diff + * + * For upsampling (e.g., 44100 -> 48000): + * - Some frames return consumed=0, meaning "I wrote it, give me same frame again" + * - This duplicates frames to increase output rate + * + * For downsampling (e.g., 48000 -> 44100): + * - When diff gets too high, frames are skipped (not written) + * - consumed=1 means "advance input even though I didn't write" + * + * @param resampler Resampler instance with state + * @param buffer Ring buffer to write to + * @param frame Input audio frame to process + * @return ResampleResult with wrote_frame and consumed flags + */ +ResampleResult AudioResampler_processFrame(AudioResampler* resampler, AudioRingBuffer* buffer, + SND_Frame frame) { + ResampleResult result = {0, 0}; + + // Decide if we should write this frame to output + if (resampler->diff < resampler->sample_rate_out) { + // Write frame to ring buffer + buffer->frames[buffer->write_pos] = frame; + buffer->write_pos++; + if (buffer->write_pos >= buffer->capacity) + buffer->write_pos = 0; + + result.wrote_frame = 1; + resampler->diff += resampler->sample_rate_in; + } + + // Decide if we've consumed an input frame + if (resampler->diff >= resampler->sample_rate_out) { + result.consumed = 1; + resampler->diff -= resampler->sample_rate_out; + } + + return result; +} + +/** + * Checks if resampling is needed for given sample rates. + * + * @param rate_in Input sample rate + * @param rate_out Output sample rate + * @return 1 if rates differ (resampling needed), 0 if rates match + */ +int AudioResampler_isNeeded(int rate_in, int rate_out) { + return rate_in != rate_out; +} diff --git a/workspace/all/common/audio_resampler.h b/workspace/all/common/audio_resampler.h new file mode 100644 index 00000000..04e76184 --- /dev/null +++ b/workspace/all/common/audio_resampler.h @@ -0,0 +1,104 @@ +/** + * audio_resampler.h - Sample rate conversion for audio + * + * Provides nearest-neighbor resampling for converting between different + * audio sample rates. Uses a Bresenham-like algorithm to determine when + * to drop or duplicate samples. + * + * Extracted from api.c for testability without SDL/ring buffer dependencies. + */ + +#ifndef __AUDIO_RESAMPLER_H__ +#define __AUDIO_RESAMPLER_H__ + +#include + +/** + * Stereo audio frame (left/right channels) + */ +typedef struct SND_Frame { + int16_t left; + int16_t right; +} SND_Frame; + +/** + * Ring buffer abstraction for audio resampling + */ +typedef struct AudioRingBuffer { + SND_Frame* frames; // Buffer array + int capacity; // Total buffer size + int write_pos; // Current write position +} AudioRingBuffer; + +/** + * Resampler state for nearest-neighbor sample rate conversion + */ +typedef struct AudioResampler { + int sample_rate_in; // Input sample rate (e.g., 44100) + int sample_rate_out; // Output sample rate (e.g., 48000) + int diff; // Accumulated error for Bresenham algorithm +} AudioResampler; + +/** + * Result of processing a frame through the resampler + */ +typedef struct ResampleResult { + int wrote_frame; // 1 if frame was written to buffer, 0 if skipped + int consumed; // 1 if should advance to next input frame, 0 if reuse +} ResampleResult; + +/** + * Initializes a resampler for given sample rates. + * + * @param resampler Resampler instance to initialize + * @param rate_in Input sample rate in Hz + * @param rate_out Output sample rate in Hz + */ +void AudioResampler_init(AudioResampler* resampler, int rate_in, int rate_out); + +/** + * Resets the resampler's internal state. + * + * @param resampler Resampler instance to reset + */ +void AudioResampler_reset(AudioResampler* resampler); + +/** + * Processes a single audio frame through nearest-neighbor resampling. + * + * Uses Bresenham-like algorithm to determine: + * 1. Whether to write this frame to the output buffer + * 2. Whether to consume (advance) the input or reuse the same frame + * + * The algorithm maintains an accumulated difference to decide when to + * write/skip frames for sample rate conversion. + * + * Example - Upsampling (44100 Hz -> 48000 Hz): + * Call 1: wrote=1, consumed=0 (write F1, reuse it) + * Call 2: wrote=1, consumed=1 (write F1 again, advance to F2) + * Call 3: wrote=1, consumed=1 (write F2, advance to F3) + * (Roughly 8.8% of frames are duplicated) + * + * Example - Downsampling (48000 Hz -> 44100 Hz): + * Call 1: wrote=1, consumed=1 (write F1, advance) + * Call 2: wrote=0, consumed=1 (skip F2, advance to F3) + * (Roughly 8.1% of frames are skipped) + * + * @param resampler Resampler instance with state + * @param buffer Ring buffer to write to + * @param frame Input audio frame to process + * @return ResampleResult indicating write/consume decisions + */ +ResampleResult AudioResampler_processFrame(AudioResampler* resampler, AudioRingBuffer* buffer, + SND_Frame frame); + +/** + * Checks if resampling is needed for given sample rates. + * + * @param rate_in Input sample rate + * @param rate_out Output sample rate + * @return 1 if rates differ (resampling needed), 0 if rates match + */ +int AudioResampler_isNeeded(int rate_in, int rate_out); + +#endif // __AUDIO_RESAMPLER_H__ diff --git a/workspace/all/common/binary_file_utils.c b/workspace/all/common/binary_file_utils.c new file mode 100644 index 00000000..192209ec --- /dev/null +++ b/workspace/all/common/binary_file_utils.c @@ -0,0 +1,46 @@ +/** + * binary_file_utils.c - Binary file I/O utilities + * + * Extracted from minarch.c for testability. + */ + +#include "binary_file_utils.h" +#include + +/** + * Reads binary data from a file. + */ +size_t BinaryFile_read(const char* filepath, void* buffer, size_t size) { + if (!filepath || !buffer || size == 0) { + return 0; + } + + FILE* file = fopen(filepath, "rb"); + if (!file) { + return 0; + } + + size_t bytes_read = fread(buffer, 1, size, file); + fclose(file); + + return bytes_read; +} + +/** + * Writes binary data to a file. + */ +size_t BinaryFile_write(const char* filepath, const void* buffer, size_t size) { + if (!filepath || !buffer || size == 0) { + return 0; + } + + FILE* file = fopen(filepath, "wb"); + if (!file) { + return 0; + } + + size_t bytes_written = fwrite(buffer, 1, size, file); + fclose(file); + + return bytes_written; +} diff --git a/workspace/all/common/binary_file_utils.h b/workspace/all/common/binary_file_utils.h new file mode 100644 index 00000000..83ceeb1a --- /dev/null +++ b/workspace/all/common/binary_file_utils.h @@ -0,0 +1,33 @@ +/** + * binary_file_utils.h - Binary file I/O utilities + * + * Simple utilities for reading/writing binary data to files. + * Extracted from minarch.c for testability and reusability. + */ + +#ifndef __BINARY_FILE_UTILS_H__ +#define __BINARY_FILE_UTILS_H__ + +#include + +/** + * Reads binary data from a file. + * + * @param filepath Path to file + * @param buffer Buffer to read into + * @param size Number of bytes to read + * @return Number of bytes actually read, 0 on error + */ +size_t BinaryFile_read(const char* filepath, void* buffer, size_t size); + +/** + * Writes binary data to a file. + * + * @param filepath Path to file + * @param buffer Buffer to write from + * @param size Number of bytes to write + * @return Number of bytes actually written, 0 on error + */ +size_t BinaryFile_write(const char* filepath, const void* buffer, size_t size); + +#endif // __BINARY_FILE_UTILS_H__ diff --git a/workspace/all/common/collection_parser.c b/workspace/all/common/collection_parser.c new file mode 100644 index 00000000..f3e82c95 --- /dev/null +++ b/workspace/all/common/collection_parser.c @@ -0,0 +1,71 @@ +/** + * collection_parser.c - Collection file parser for custom ROM lists + * + * Extracted from minui.c for testability. + */ + +#define _POSIX_C_SOURCE 200809L // Required for strdup() + +#include "collection_parser.h" +#include "utils.h" +#include +#include +#include + +/** + * Parses a collection file and returns valid ROM entries. + * + * Reads the collection .txt file, validates each ROM exists, and + * creates entries for valid ROMs only. + * + * @param collection_path Full path to collection .txt file + * @param sdcard_path SDCARD_PATH constant + * @param entry_count Output: number of entries found + * @return Array of Collection_Entry* (caller must free with Collection_freeEntries) + */ +Collection_Entry** Collection_parse(char* collection_path, const char* sdcard_path, + int* entry_count) { + *entry_count = 0; + + // Allocate space for up to 100 entries + Collection_Entry** entries = malloc(sizeof(Collection_Entry*) * 100); + + FILE* file = fopen(collection_path, "r"); + if (file) { + char line[256]; + while (fgets(line, 256, file) != NULL && *entry_count < 100) { + normalizeNewline(line); + trimTrailingNewlines(line); + if (strlen(line) == 0) + continue; // skip empty lines + + // Construct full path (collection paths are relative to sdcard) + char sd_path[256]; + sprintf(sd_path, "%s%s", sdcard_path, line); + + // Only include ROMs that exist + if (exists(sd_path)) { + Collection_Entry* entry = malloc(sizeof(Collection_Entry)); + entry->path = strdup(sd_path); + entry->is_pak = suffixMatch(".pak", sd_path); + + entries[*entry_count] = entry; + (*entry_count)++; + } + } + fclose(file); + } + + return entries; +} + +/** + * Frees entry array returned by Collection_parse() + */ +void Collection_freeEntries(Collection_Entry** entries, int count) { + for (int i = 0; i < count; i++) { + free(entries[i]->path); + free(entries[i]); + } + free(entries); +} diff --git a/workspace/all/common/collection_parser.h b/workspace/all/common/collection_parser.h new file mode 100644 index 00000000..1a80f91f --- /dev/null +++ b/workspace/all/common/collection_parser.h @@ -0,0 +1,56 @@ +/** + * collection_parser.h - Collection file parser for custom ROM lists + * + * MinUI supports ".txt" collection files that list specific ROMs to display. + * Format: One ROM path per line (relative to SDCARD_PATH) + * + * Example collection file (/Collections/Favorites.txt): + * /Roms/GB/mario.gb + * /Roms/NES/zelda.nes + * /Roms/SNES/metroid.smc + * + * Extracted from minui.c for testability. + */ + +#ifndef __COLLECTION_PARSER_H__ +#define __COLLECTION_PARSER_H__ + +/** + * ROM entry from collection file + */ +typedef struct Collection_Entry { + char* path; // Full path to ROM + int is_pak; // 1 if .pak file, 0 if ROM +} Collection_Entry; + +/** + * Parses a collection file and returns valid ROM entries. + * + * Reads the collection .txt file, validates each ROM exists, and + * creates entries for valid ROMs only. + * + * Example: + * File: "/Collections/Action.txt" containing "/Roms/GB/mario.gb" + * Returns: Array with one entry for mario.gb (if it exists) + * + * @param collection_path Full path to collection .txt file + * @param sdcard_path SDCARD_PATH constant (e.g., "/mnt/SDCARD") + * @param entry_count Output: number of entries found + * @return Array of Collection_Entry* (caller must free with Collection_freeEntries) + * + * @note Only includes ROMs that exist on filesystem + * @note Skips empty lines + * @note Paths in collection file are relative to sdcard_path + */ +Collection_Entry** Collection_parse(char* collection_path, const char* sdcard_path, + int* entry_count); + +/** + * Frees entry array returned by Collection_parse() + * + * @param entries Array of entry pointers + * @param count Number of entries in array + */ +void Collection_freeEntries(Collection_Entry** entries, int count); + +#endif // __COLLECTION_PARSER_H__ diff --git a/workspace/all/common/collections.c b/workspace/all/common/collections.c new file mode 100644 index 00000000..7645c8ff --- /dev/null +++ b/workspace/all/common/collections.c @@ -0,0 +1,193 @@ +/** + * collections.c - Generic data structures for MinUI + * + * Provides Array (dynamic array) and Hash (key-value map) data structures. + * Extracted from minui.c for better testability and reusability. + */ + +#include "collections.h" +#include "utils.h" +#include +#include + +/////////////////////////////// +// Dynamic array implementation +/////////////////////////////// + +/** + * Creates a new empty array. + * + * @return Pointer to allocated Array + * + * @warning Caller must free with Array_free() or type-specific free function + */ +Array* Array_new(void) { + Array* self = malloc(sizeof(Array)); + self->count = 0; + self->capacity = 8; + self->items = malloc(sizeof(void*) * self->capacity); + return self; +} + +/** + * Appends an item to the end of the array. + * + * Automatically doubles capacity when full. + * + * @param self Array to modify + * @param item Pointer to add (not copied, caller retains ownership) + */ +void Array_push(Array* self, void* item) { + if (self->count >= self->capacity) { + self->capacity *= 2; + self->items = realloc(self->items, sizeof(void*) * self->capacity); + } + self->items[self->count++] = item; +} + +/** + * Inserts an item at the beginning of the array. + * + * Shifts all existing items to the right. Used to add most + * recent game to top of recents list. + * + * @param self Array to modify + * @param item Pointer to insert + */ +void Array_unshift(Array* self, void* item) { + if (self->count == 0) + return Array_push(self, item); + Array_push(self, NULL); // ensures we have enough capacity + for (int i = self->count - 2; i >= 0; i--) { + self->items[i + 1] = self->items[i]; + } + self->items[0] = item; +} + +/** + * Removes and returns the last item from the array. + * + * @param self Array to modify + * @return Pointer to removed item, or NULL if array is empty + * + * @note Caller assumes ownership of returned pointer + */ +void* Array_pop(Array* self) { + if (self->count == 0) + return NULL; + return self->items[--self->count]; +} + +/** + * Reverses the order of all items in the array. + * + * @param self Array to modify + */ +void Array_reverse(Array* self) { + int end = self->count - 1; + int mid = self->count / 2; + for (int i = 0; i < mid; i++) { + void* item = self->items[i]; + self->items[i] = self->items[end - i]; + self->items[end - i] = item; + } +} + +/** + * Frees the array structure. + * + * @param self Array to free + * + * @warning Does NOT free the items themselves - use type-specific free functions + */ +void Array_free(Array* self) { + free(self->items); + free(self); +} + +/** + * Finds the index of a string in a string array. + * + * @param self Array of string pointers + * @param str String to find + * @return Index of first matching string, or -1 if not found + */ +int StringArray_indexOf(Array* self, char* str) { + for (int i = 0; i < self->count; i++) { + if (exactMatch(self->items[i], str)) + return i; + } + return -1; +} + +/** + * Frees a string array and all strings it contains. + * + * @param self Array to free + */ +void StringArray_free(Array* self) { + for (int i = 0; i < self->count; i++) { + free(self->items[i]); + } + Array_free(self); +} + +/////////////////////////////// +// Simple hash map (key-value store) +/////////////////////////////// + +/** + * Creates a new empty hash map. + * + * @return Pointer to allocated Hash + * + * @warning Caller must free with Hash_free() + */ +Hash* Hash_new(void) { + Hash* self = malloc(sizeof(Hash)); + self->keys = Array_new(); + self->values = Array_new(); + return self; +} + +/** + * Frees a hash map and all its keys and values. + * + * @param self Hash to free + */ +void Hash_free(Hash* self) { + StringArray_free(self->keys); + StringArray_free(self->values); + free(self); +} + +/** + * Stores a key-value pair in the hash map. + * + * Both key and value are duplicated with strdup(). + * Does not check for duplicate keys - allows multiple entries. + * + * @param self Hash to modify + * @param key Key string + * @param value Value string + */ +void Hash_set(Hash* self, char* key, char* value) { + Array_push(self->keys, strdup(key)); + Array_push(self->values, strdup(value)); +} + +/** + * Retrieves a value by key from the hash map. + * + * @param self Hash to search + * @param key Key to look up + * @return Pointer to value string, or NULL if key not found + * + * @note Returned pointer is owned by the Hash - do not free + */ +char* Hash_get(Hash* self, char* key) { + int i = StringArray_indexOf(self->keys, key); + if (i == -1) + return NULL; + return self->values->items[i]; +} diff --git a/workspace/all/common/collections.h b/workspace/all/common/collections.h new file mode 100644 index 00000000..7af53e32 --- /dev/null +++ b/workspace/all/common/collections.h @@ -0,0 +1,156 @@ +/** + * collections.h - Generic data structures for MinUI + * + * Provides Array (dynamic array) and Hash (key-value map) data structures. + * Extracted from minui.c for better testability and reusability. + * + * Array: Generic dynamic array that stores void pointers + * Hash: Simple key-value map using parallel string arrays + */ + +#ifndef __COLLECTIONS_H__ +#define __COLLECTIONS_H__ + +/////////////////////////////// +// Dynamic array +/////////////////////////////// + +/** + * Generic dynamic array with automatic growth. + * + * Stores pointers to any type. Initial capacity is 8, + * doubles when full. Used for directories, entries, and recents. + */ +typedef struct Array { + int count; + int capacity; + void** items; +} Array; + +/** + * Creates a new empty array. + * + * @return Pointer to allocated Array + * + * @warning Caller must free with Array_free() or type-specific free function + */ +Array* Array_new(void); + +/** + * Appends an item to the end of the array. + * + * Automatically doubles capacity when full. + * + * @param self Array to modify + * @param item Pointer to add (not copied, caller retains ownership) + */ +void Array_push(Array* self, void* item); + +/** + * Inserts an item at the beginning of the array. + * + * Shifts all existing items to the right. Used to add most + * recent game to top of recents list. + * + * @param self Array to modify + * @param item Pointer to insert + */ +void Array_unshift(Array* self, void* item); + +/** + * Removes and returns the last item from the array. + * + * @param self Array to modify + * @return Pointer to removed item, or NULL if array is empty + * + * @note Caller assumes ownership of returned pointer + */ +void* Array_pop(Array* self); + +/** + * Reverses the order of all items in the array. + * + * @param self Array to modify + */ +void Array_reverse(Array* self); + +/** + * Frees the array structure. + * + * @param self Array to free + * + * @warning Does NOT free the items themselves - use type-specific free functions + */ +void Array_free(Array* self); + +/** + * Finds the index of a string in a string array. + * + * @param self Array of string pointers + * @param str String to find + * @return Index of first matching string, or -1 if not found + */ +int StringArray_indexOf(Array* self, char* str); + +/** + * Frees a string array and all strings it contains. + * + * @param self Array to free + */ +void StringArray_free(Array* self); + +/////////////////////////////// +// Simple hash map (key-value store) +/////////////////////////////// + +/** + * Simple key-value map using parallel arrays. + * + * Used for loading map.txt files that alias ROM display names. + * Not a true hash - just linear search through keys array. + */ +typedef struct Hash { + Array* keys; + Array* values; +} Hash; + +/** + * Creates a new empty hash map. + * + * @return Pointer to allocated Hash + * + * @warning Caller must free with Hash_free() + */ +Hash* Hash_new(void); + +/** + * Frees a hash map and all its keys and values. + * + * @param self Hash to free + */ +void Hash_free(Hash* self); + +/** + * Stores a key-value pair in the hash map. + * + * Both key and value are duplicated with strdup(). + * Does not check for duplicate keys - allows multiple entries. + * + * @param self Hash to modify + * @param key Key string + * @param value Value string + */ +void Hash_set(Hash* self, char* key, char* value); + +/** + * Retrieves a value by key from the hash map. + * + * @param self Hash to search + * @param key Key to look up + * @return Pointer to value string, or NULL if key not found + * + * @note Returned pointer is owned by the Hash - do not free + */ +char* Hash_get(Hash* self, char* key); + +#endif // __COLLECTIONS_H__ diff --git a/workspace/all/common/gfx_text.c b/workspace/all/common/gfx_text.c new file mode 100644 index 00000000..5b631548 --- /dev/null +++ b/workspace/all/common/gfx_text.c @@ -0,0 +1,182 @@ +/** + * gfx_text.c - Text rendering utilities for MinUI + * + * Provides text manipulation functions for the graphics system. + * Extracted from api.c for better testability and reusability. + */ + +// Include appropriate headers based on build mode +#ifdef UNIT_TEST_BUILD +// Test mode: include SDL stubs to get TTF_Font typedef +// (sdl_stubs.h available via -I tests/support in test builds) +#include "sdl_stubs.h" +#else +// Production mode: include api.h to get SDL types +#include "api.h" +#endif + +#include "gfx_text.h" +#include "utils.h" +#include + +// TTF_SizeUTF8 is provided by SDL_ttf in production +// In tests, it's mocked with fff (declared in sdl_fakes.h) +#ifndef UNIT_TEST_BUILD +extern int TTF_SizeUTF8(TTF_Font* font, const char* text, int* w, int* h); +#endif + +// Constants - typically defined in platform headers +#ifndef MAX_PATH +#define MAX_PATH 512 +#endif + +#ifndef MAX_TEXT_LINES +#define MAX_TEXT_LINES 16 +#endif + +/** + * Truncates text to fit within a maximum width. + * + * If the text (plus padding) exceeds max_width, characters are removed from + * the end and replaced with "..." until it fits. + * + * @param font TTF font used to measure text + * @param in_name Input text to truncate + * @param out_name Output buffer for truncated text (min 256 bytes) + * @param max_width Maximum width in pixels + * @param padding Additional padding to account for in pixels + * @return Final width of truncated text including padding + */ +int GFX_truncateText(TTF_Font* font, const char* in_name, char* out_name, int max_width, + int padding) { + int text_width; + strcpy(out_name, in_name); + TTF_SizeUTF8(font, out_name, &text_width, NULL); + text_width += padding; + + while (text_width > max_width) { + int len = strlen(out_name); + strcpy(&out_name[len - 4], "...\0"); + TTF_SizeUTF8(font, out_name, &text_width, NULL); + text_width += padding; + } + + return text_width; +} + +/** + * Wraps text to fit within a maximum width by inserting newlines. + * + * Breaks text at space characters to create wrapped lines. The last + * line is truncated with "..." if it still exceeds max_width. + * Modifies the input string in place by replacing spaces with newlines. + * + * @param font TTF font to measure text with + * @param str String to wrap (modified in place) + * @param max_width Maximum width per line in pixels + * @param max_lines Maximum number of lines (0 for unlimited) + * @return Width of the widest line in pixels + * + * @note Input string is modified - spaces become newlines at wrap points + */ +int GFX_wrapText(TTF_Font* font, char* str, int max_width, int max_lines) { + if (!str) + return 0; + + int line_width; + int max_line_width = 0; + char* line = str; + char buffer[MAX_PATH]; + + TTF_SizeUTF8(font, line, &line_width, NULL); + if (line_width <= max_width) { + line_width = GFX_truncateText(font, line, buffer, max_width, 0); + strcpy(line, buffer); + return line_width; + } + + char* prev = NULL; + char* tmp = line; + int lines = 1; + while (!max_lines || lines < max_lines) { + tmp = strchr(tmp, ' '); + if (!tmp) { + if (prev) { + TTF_SizeUTF8(font, line, &line_width, NULL); + if (line_width >= max_width) { + if (line_width > max_line_width) + max_line_width = line_width; + prev[0] = '\n'; + line = prev + 1; + } + } + break; + } + tmp[0] = '\0'; + + TTF_SizeUTF8(font, line, &line_width, NULL); + + if (line_width >= max_width) { // wrap + if (line_width > max_line_width) + max_line_width = line_width; + tmp[0] = ' '; + tmp += 1; + prev[0] = '\n'; + prev += 1; + line = prev; + lines += 1; + } else { // continue + tmp[0] = ' '; + prev = tmp; + tmp += 1; + } + } + + line_width = GFX_truncateText(font, line, buffer, max_width, 0); + strcpy(line, buffer); + + if (line_width > max_line_width) + max_line_width = line_width; + return max_line_width; +} + +/** + * Calculates the bounding box size of multi-line text. + * + * Measures the width and height needed to render text that may contain + * newlines. Width is set to the widest line, height is line count * leading. + * + * @param font TTF font to measure text with + * @param str Text to measure (may contain \n for newlines) + * @param leading Line spacing in pixels (line height) + * @param w Output: Width of widest line in pixels + * @param h Output: Total height (line count * leading) + */ +void GFX_sizeText(TTF_Font* font, char* str, int leading, int* w, int* h) { + char* lines[MAX_TEXT_LINES]; + int count = splitTextLines(str, lines, MAX_TEXT_LINES); + *h = count * leading; + + int mw = 0; + char line[256]; + for (int i = 0; i < count; i++) { + int len; + if (i + 1 < count) { + len = lines[i + 1] - lines[i] - 1; + if (len) + strncpy(line, lines[i], len); + line[len] = '\0'; + } else { + len = strlen(lines[i]); + strcpy(line, lines[i]); + } + + if (len) { + int lw; + TTF_SizeUTF8(font, line, &lw, NULL); + if (lw > mw) + mw = lw; + } + } + *w = mw; +} diff --git a/workspace/all/common/gfx_text.h b/workspace/all/common/gfx_text.h new file mode 100644 index 00000000..d9c6d45e --- /dev/null +++ b/workspace/all/common/gfx_text.h @@ -0,0 +1,82 @@ +/** + * gfx_text.h - Text rendering utilities for MinUI + * + * Provides text manipulation functions for the graphics system: + * - Text truncation with ellipsis + * - Multi-line text wrapping + * + * These are pure logic functions extracted from api.c for testability. + * They depend only on TTF_SizeUTF8 for measuring text width. + */ + +#ifndef __GFX_TEXT_H__ +#define __GFX_TEXT_H__ + +// TTF_Font type must be defined before including this header: +// - Production: api.h includes SDL_ttf.h then this header +// - Tests: gfx_text.c includes sdl_stubs.h when SDL_STUBS_H is defined + +/** + * Truncates text to fit within a maximum width. + * + * If the text (plus padding) exceeds max_width, characters are removed from + * the end and replaced with "..." until it fits. The output string is modified + * in place. + * + * Example: + * "The Legend of Zelda" -> "The Legend..." + * + * @param font TTF font used to measure text + * @param in_name Input text to truncate + * @param out_name Output buffer for truncated text (min 256 bytes) + * @param max_width Maximum width in pixels + * @param padding Additional padding to account for in pixels + * @return Final width of truncated text including padding + * + * @note out_name buffer must have space for at least "..." (4 bytes including null) + */ +int GFX_truncateText(TTF_Font* font, const char* in_name, char* out_name, int max_width, + int padding); + +/** + * Wraps text to fit within a maximum width by inserting newlines. + * + * Breaks text at space characters to create wrapped lines. The last + * line is truncated with "..." if it still exceeds max_width. + * Modifies the input string in place by replacing spaces with newlines. + * + * Example: + * "The quick brown fox" -> "The quick\nbrown fox" + * + * @param font TTF font to measure text with + * @param str String to wrap (modified in place) + * @param max_width Maximum width per line in pixels + * @param max_lines Maximum number of lines (0 for unlimited) + * @return Width of the widest line in pixels + * + * @warning Input string is modified - spaces become newlines at wrap points + * @note Requires MAX_PATH to be defined (typically 512) + */ +int GFX_wrapText(TTF_Font* font, char* str, int max_width, int max_lines); + +/** + * Calculates the bounding box size of multi-line text. + * + * Measures the width and height needed to render text that may contain + * newlines. Width is set to the widest line, height is line count * leading. + * + * Example: + * "Line 1\nLonger Line 2\nLine 3" with leading=16 + * -> w = width of "Longer Line 2", h = 48 (3 lines * 16px) + * + * @param font TTF font to measure text with + * @param str Text to measure (may contain \n for newlines) + * @param leading Line spacing in pixels (line height) + * @param w Output: Width of widest line in pixels + * @param h Output: Total height (line count * leading) + * + * @note Maximum 16 lines supported (MAX_TEXT_LINES) + */ +void GFX_sizeText(TTF_Font* font, char* str, int leading, int* w, int* h); + +#endif // __GFX_TEXT_H__ diff --git a/workspace/all/common/m3u_parser.c b/workspace/all/common/m3u_parser.c new file mode 100644 index 00000000..abc1617e --- /dev/null +++ b/workspace/all/common/m3u_parser.c @@ -0,0 +1,133 @@ +/** + * m3u_parser.c - M3U playlist parser for multi-disc games + * + * Extracted from minui.c for testability. + */ + +#define _POSIX_C_SOURCE 200809L // Required for strdup() + +#include "m3u_parser.h" +#include "utils.h" +#include +#include +#include + +/** + * Gets the path to the first disc in an M3U playlist. + * + * Reads the M3U file, finds the first non-empty line, constructs + * the full path to the disc image, and verifies it exists. + * + * @param m3u_path Full path to the .m3u file + * @param disc_path Output buffer for first disc path (min 256 bytes) + * @return 1 if first disc found and exists, 0 otherwise + */ +int M3U_getFirstDisc(char* m3u_path, char* disc_path) { + int found = 0; + + // Extract base directory from M3U path + char base_path[256]; + strcpy(base_path, m3u_path); + char* tmp = strrchr(base_path, '/'); + if (tmp) { + tmp += 1; + tmp[0] = '\0'; // Terminate at the slash, keeping directory + } + + // Open and parse M3U file + FILE* file = fopen(m3u_path, "r"); + if (file) { + char line[256]; + while (fgets(line, 256, file) != NULL) { + normalizeNewline(line); + trimTrailingNewlines(line); + if (strlen(line) == 0) + continue; // skip empty lines + + // Construct full disc path (M3U paths are relative to M3U location) + sprintf(disc_path, "%s%s", base_path, line); + + // Verify disc exists + if (exists(disc_path)) + found = 1; + break; // Only need first disc + } + fclose(file); + } + return found; +} + +/** + * Gets all discs from an M3U playlist. + * + * Reads the M3U file and creates a list of all valid disc entries. + * Each disc is numbered sequentially (Disc 1, Disc 2, etc.). + * + * @param m3u_path Full path to .m3u file + * @param disc_count Output: number of discs found + * @return Array of M3U_Disc* (caller must free with M3U_freeDiscs) + */ +M3U_Disc** M3U_getAllDiscs(char* m3u_path, int* disc_count) { + *disc_count = 0; + + // Allocate space for up to 10 discs + M3U_Disc** discs = malloc(sizeof(M3U_Disc*) * 10); + + // Extract base directory from M3U path + char base_path[256]; + strcpy(base_path, m3u_path); + char* tmp = strrchr(base_path, '/'); + if (tmp) { + tmp += 1; + tmp[0] = '\0'; + } + + // Read M3U file + FILE* file = fopen(m3u_path, "r"); + if (file) { + char line[256]; + int disc_num = 0; + + while (fgets(line, 256, file) != NULL && *disc_count < 10) { + normalizeNewline(line); + trimTrailingNewlines(line); + if (strlen(line) == 0) + continue; // skip empty lines + + // Construct full disc path + char disc_path[256]; + sprintf(disc_path, "%s%s", base_path, line); + + // Only include discs that exist + if (exists(disc_path)) { + disc_num++; + + M3U_Disc* disc = malloc(sizeof(M3U_Disc)); + disc->path = strdup(disc_path); + + char name[16]; + sprintf(name, "Disc %i", disc_num); + disc->name = strdup(name); + disc->disc_number = disc_num; + + discs[*disc_count] = disc; + (*disc_count)++; + } + } + fclose(file); + } + + return discs; +} + +/** + * Frees disc array returned by M3U_getAllDiscs() + */ +void M3U_freeDiscs(M3U_Disc** discs, int count) { + for (int i = 0; i < count; i++) { + free(discs[i]->path); + free(discs[i]->name); + free(discs[i]); + } + free(discs); +} diff --git a/workspace/all/common/m3u_parser.h b/workspace/all/common/m3u_parser.h new file mode 100644 index 00000000..6ebcc821 --- /dev/null +++ b/workspace/all/common/m3u_parser.h @@ -0,0 +1,73 @@ +/** + * m3u_parser.h - M3U playlist parser for multi-disc games + * + * M3U files list disc images for multi-disc games (e.g., Final Fantasy VII). + * Format: Plain text file with one disc path per line. + * + * Example .m3u file: + * FF7 (Disc 1).bin + * FF7 (Disc 2).bin + * FF7 (Disc 3).bin + * + * Extracted from minui.c for testability. + */ + +#ifndef __M3U_PARSER_H__ +#define __M3U_PARSER_H__ + +/** + * Gets the path to the first disc in an M3U playlist. + * + * Reads the M3U file, finds the first non-empty line, constructs + * the full path to the disc image, and verifies it exists. + * + * Example: + * M3U file: /Roms/PS1/FF7.m3u containing "FF7 (Disc 1).bin" + * Returns: /Roms/PS1/FF7 (Disc 1).bin + * + * @param m3u_path Full path to the .m3u file + * @param disc_path Output buffer for first disc path (min 256 bytes) + * @return 1 if first disc found and exists, 0 otherwise + * + * @note Skips empty lines in the M3U file + * @note Disc paths in M3U are relative to the M3U file's directory + */ +int M3U_getFirstDisc(char* m3u_path, char* disc_path); + +/** + * Disc entry structure for M3U disc lists + */ +typedef struct M3U_Disc { + char* path; // Full path to disc file + char* name; // Display name (e.g., "Disc 1", "Disc 2") + int disc_number; // 1-based disc number +} M3U_Disc; + +/** + * Gets all discs from an M3U playlist. + * + * Reads the M3U file and creates a list of all valid disc entries. + * Each disc is numbered sequentially (Disc 1, Disc 2, etc.). + * + * Example: + * M3U: "disc1.bin\ndisc2.bin\ndisc3.bin" + * Returns array with 3 discs named "Disc 1", "Disc 2", "Disc 3" + * + * @param m3u_path Full path to .m3u file + * @param disc_count Output: number of discs found + * @return Array of M3U_Disc* (caller must free with M3U_freeDiscs) + * + * @note Only includes discs that exist on filesystem + * @note Caller must free returned array and disc structures + */ +M3U_Disc** M3U_getAllDiscs(char* m3u_path, int* disc_count); + +/** + * Frees disc array returned by M3U_getAllDiscs() + * + * @param discs Array of disc pointers + * @param count Number of discs in array + */ +void M3U_freeDiscs(M3U_Disc** discs, int count); + +#endif // __M3U_PARSER_H__ diff --git a/workspace/all/common/map_parser.c b/workspace/all/common/map_parser.c new file mode 100644 index 00000000..41847a3b --- /dev/null +++ b/workspace/all/common/map_parser.c @@ -0,0 +1,69 @@ +/** + * map_parser.c - ROM display name aliasing via map.txt files + * + * Extracted from minarch.c for testability. + */ + +#include "map_parser.h" +#include "utils.h" +#include +#include + +/** + * Looks up the display alias for a ROM file from map.txt. + * + * Searches for map.txt in the same directory as the ROM file. + * If found, looks up the ROM's filename in the map and returns the alias. + * + * @param path Full path to ROM file + * @param alias Output buffer for alias (min 256 bytes) + * @return Pointer to alias buffer (even if no alias found) + */ +char* Map_getAlias(char* path, char* alias) { + char* tmp; + char map_path[256]; + strcpy(map_path, path); + + // Replace filename with "map.txt" + tmp = strrchr(map_path, '/'); + if (tmp) { + tmp += 1; + strcpy(tmp, "map.txt"); + } + + // Extract filename from full path + char* file_name = strrchr(path, '/'); + if (file_name) + file_name += 1; + + // Read map.txt if it exists + if (exists(map_path)) { + FILE* file = fopen(map_path, "r"); + if (file) { + char line[256]; + while (fgets(line, 256, file) != NULL) { + normalizeNewline(line); + trimTrailingNewlines(line); + if (strlen(line) == 0) + continue; // skip empty lines + + // Parse tab-delimited format: filenamealias + tmp = strchr(line, '\t'); + if (tmp) { + tmp[0] = '\0'; + char* key = line; + char* value = tmp + 1; + + // Found matching ROM? + if (exactMatch(file_name, key)) { + strcpy(alias, value); + break; + } + } + } + fclose(file); + } + } + + return alias; +} diff --git a/workspace/all/common/map_parser.h b/workspace/all/common/map_parser.h new file mode 100644 index 00000000..bb1b95aa --- /dev/null +++ b/workspace/all/common/map_parser.h @@ -0,0 +1,37 @@ +/** + * map_parser.h - ROM display name aliasing via map.txt files + * + * MinUI uses map.txt files to provide custom display names for ROMs. + * Format: Tab-delimited key-value pairs + * mario.gbSuper Mario Land + * zelda.gbLink's Awakening + * + * If the alias starts with '.', the ROM is hidden from display. + * + * Extracted from minarch.c and minui.c for testability. + */ + +#ifndef __MAP_PARSER_H__ +#define __MAP_PARSER_H__ + +/** + * Looks up the display alias for a ROM file from map.txt. + * + * Searches for map.txt in the same directory as the ROM file. + * If found, looks up the ROM's filename in the map and returns the alias. + * + * Example: + * ROM: /Roms/GB/mario.gb + * map.txt: "mario.gb\tSuper Mario Land" + * Returns: "Super Mario Land" (written to alias buffer) + * + * @param path Full path to ROM file + * @param alias Output buffer for alias (min 256 bytes) + * @return Pointer to alias buffer (even if no alias found, returns same buffer) + * + * @note If no map.txt exists or ROM not found in map, alias is unchanged + * @note Tab-delimited format: filenamedisplay name + */ +char* Map_getAlias(char* path, char* alias); + +#endif // __MAP_PARSER_H__ diff --git a/workspace/all/common/minarch_paths.c b/workspace/all/common/minarch_paths.c new file mode 100644 index 00000000..d259d3a2 --- /dev/null +++ b/workspace/all/common/minarch_paths.c @@ -0,0 +1,78 @@ +/** + * minarch_paths.c - Path generation utilities for MinArch save files + * + * Provides consistent path generation for save data files. + * Extracted from minarch.c for testability. + */ + +#include "minarch_paths.h" +#include +#include + +/** + * Generates path for SRAM (battery save) file. + * + * Format: {saves_dir}/{game_name}.sav + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param saves_dir Directory for save files + * @param game_name Game name (without extension) + */ +void MinArch_getSRAMPath(char* filename, const char* saves_dir, const char* game_name) { + sprintf(filename, "%s/%s.sav", saves_dir, game_name); +} + +/** + * Generates path for RTC (real-time clock) file. + * + * Format: {saves_dir}/{game_name}.rtc + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param saves_dir Directory for save files + * @param game_name Game name (without extension) + */ +void MinArch_getRTCPath(char* filename, const char* saves_dir, const char* game_name) { + sprintf(filename, "%s/%s.rtc", saves_dir, game_name); +} + +/** + * Generates path for save state file. + * + * Format: {states_dir}/{game_name}.st{slot} + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param states_dir Directory for state files + * @param game_name Game name (without extension) + * @param slot Save state slot number (0-9) + */ +void MinArch_getStatePath(char* filename, const char* states_dir, const char* game_name, int slot) { + sprintf(filename, "%s/%s.st%i", states_dir, game_name, slot); +} + +/** + * Generates path for configuration file. + * + * Format (game-specific): {config_dir}/{game_name}{device_tag}.cfg + * Format (global): {config_dir}/minarch{device_tag}.cfg + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param config_dir Directory for config files + * @param game_name Game name (NULL for global config) + * @param device_tag Device-specific tag (NULL or "" if none, e.g., "-rg35xx") + */ +void MinArch_getConfigPath(char* filename, const char* config_dir, const char* game_name, + const char* device_tag) { + char device_suffix[64] = {0}; + + // Build device tag suffix if provided + if (device_tag && device_tag[0] != '\0') { + sprintf(device_suffix, "-%s", device_tag); + } + + // Generate path based on game-specific or global + if (game_name) { + sprintf(filename, "%s/%s%s.cfg", config_dir, game_name, device_suffix); + } else { + sprintf(filename, "%s/minarch%s.cfg", config_dir, device_suffix); + } +} diff --git a/workspace/all/common/minarch_paths.h b/workspace/all/common/minarch_paths.h new file mode 100644 index 00000000..f418b905 --- /dev/null +++ b/workspace/all/common/minarch_paths.h @@ -0,0 +1,70 @@ +/** + * minarch_paths.h - Path generation utilities for MinArch save files + * + * Provides functions to generate consistent file paths for save data: + * - SRAM (battery-backed save RAM, .sav files) + * - RTC (real-time clock data, .rtc files) + * - Save states (.st0-.st9 files) + * - Configuration files (.cfg files) + * + * Extracted from minarch.c for testability. + */ + +#ifndef __MINARCH_PATHS_H__ +#define __MINARCH_PATHS_H__ + +/** + * Generates path for SRAM (battery save) file. + * + * Format: {saves_dir}/{game_name}.sav + * Example: "/mnt/SDCARD/.userdata/miyoomini/gpsp/Pokemon.sav" + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param saves_dir Directory for save files + * @param game_name Game name (without extension) + */ +void MinArch_getSRAMPath(char* filename, const char* saves_dir, const char* game_name); + +/** + * Generates path for RTC (real-time clock) file. + * + * Format: {saves_dir}/{game_name}.rtc + * Example: "/mnt/SDCARD/.userdata/miyoomini/gpsp/Pokemon.rtc" + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param saves_dir Directory for save files + * @param game_name Game name (without extension) + */ +void MinArch_getRTCPath(char* filename, const char* saves_dir, const char* game_name); + +/** + * Generates path for save state file. + * + * Format: {states_dir}/{game_name}.st{slot} + * Example: "/mnt/SDCARD/.userdata/miyoomini/gpsp/Pokemon.st0" + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param states_dir Directory for state files + * @param game_name Game name (without extension) + * @param slot Save state slot number (0-9) + */ +void MinArch_getStatePath(char* filename, const char* states_dir, const char* game_name, int slot); + +/** + * Generates path for configuration file. + * + * Format (game-specific): {config_dir}/{game_name}{device_tag}.cfg + * Format (global): {config_dir}/minarch{device_tag}.cfg + * + * Example: "/mnt/SDCARD/.userdata/miyoomini/gpsp/Pokemon-rg35xx.cfg" + * Example: "/mnt/SDCARD/.userdata/miyoomini/gpsp/minarch-rg35xx.cfg" + * + * @param filename Output buffer (min MAX_PATH bytes) + * @param config_dir Directory for config files + * @param game_name Game name (NULL for global config) + * @param device_tag Device-specific tag (NULL or "" if none, e.g., "-rg35xx") + */ +void MinArch_getConfigPath(char* filename, const char* config_dir, const char* game_name, + const char* device_tag); + +#endif // __MINARCH_PATHS_H__ diff --git a/workspace/all/common/minui_file_utils.c b/workspace/all/common/minui_file_utils.c new file mode 100644 index 00000000..0de8f361 --- /dev/null +++ b/workspace/all/common/minui_file_utils.c @@ -0,0 +1,135 @@ +/** + * minui_file_utils.c - File and directory checking utilities for MinUI + * + * Extracted from minui.c for testability. + */ + +#include "minui_file_utils.h" +#include "utils.h" +#include +#include +#include + +/** + * Checks if an emulator exists for a given system. + * + * Searches for emulator .pak directories in two locations: + * 1. {paks_path}/Emus/{emu_name}.pak/launch.sh (shared location) + * 2. {sdcard_path}/Emus/{platform}/{emu_name}.pak/launch.sh (platform-specific) + * + * @param emu_name Emulator name (e.g., "gpsp", "gambatte") + * @param paks_path PAKS_PATH constant + * @param sdcard_path SDCARD_PATH constant + * @param platform PLATFORM constant + * @return 1 if emulator exists, 0 otherwise + */ +int MinUI_hasEmu(char* emu_name, const char* paks_path, const char* sdcard_path, + const char* platform) { + char pak_path[256]; + + // Try shared location first + sprintf(pak_path, "%s/Emus/%s.pak/launch.sh", paks_path, emu_name); + if (exists(pak_path)) + return 1; + + // Try platform-specific location + sprintf(pak_path, "%s/Emus/%s/%s.pak/launch.sh", sdcard_path, platform, emu_name); + return exists(pak_path); +} + +/** + * Checks if a directory contains a .cue file for disc-based games. + * + * The .cue file must be named after the directory itself. + * + * @param dir_path Full path to directory + * @param cue_path Output buffer for .cue file path + * @return 1 if .cue file exists, 0 otherwise + */ +int MinUI_hasCue(char* dir_path, char* cue_path) { + char* tmp = strrchr(dir_path, '/'); + if (!tmp) + return 0; + + tmp += 1; // Move past the slash to get folder name + sprintf(cue_path, "%s/%s.cue", dir_path, tmp); + return exists(cue_path); +} + +/** + * Checks if a ROM has an associated .m3u playlist for multi-disc games. + * + * The .m3u file must be in the parent directory and named after the game directory. + * + * @param rom_path Full path to ROM file + * @param m3u_path Output buffer for .m3u file path + * @return 1 if .m3u file exists, 0 otherwise + */ +int MinUI_hasM3u(char* rom_path, char* m3u_path) { + char* tmp; + + // Start with rom_path: /Roms/PS1/FF7/disc1.bin + strcpy(m3u_path, rom_path); + + // Remove filename to get directory: /Roms/PS1/FF7/ + tmp = strrchr(m3u_path, '/'); + if (!tmp) + return 0; + tmp += 1; + tmp[0] = '\0'; + + // Remove trailing slash: /Roms/PS1/FF7 + tmp = strrchr(m3u_path, '/'); + if (!tmp) + return 0; + tmp[0] = '\0'; + + // Get the game directory name before removing it + char dir_name[256]; + tmp = strrchr(m3u_path, '/'); + if (!tmp) + return 0; + strcpy(dir_name, tmp); // dir_name = "/FF7" + + // Remove the game directory: /Roms/PS1 + tmp[0] = '\0'; + + // Append game directory name: /Roms/PS1/FF7 + tmp = m3u_path + strlen(m3u_path); + strcpy(tmp, dir_name); + + // Add extension: /Roms/PS1/FF7.m3u + tmp = m3u_path + strlen(m3u_path); + strcpy(tmp, ".m3u"); + + return exists(m3u_path); +} + +/** + * Checks if a directory contains any non-hidden files. + * + * @param dir_path Full path to directory + * @return 1 if directory exists and contains non-hidden files, 0 otherwise + */ +int MinUI_hasNonHiddenFiles(const char* dir_path) { + if (!exists((char*)dir_path)) { + return 0; + } + + DIR* dh = opendir(dir_path); + if (!dh) { + return 0; + } + + struct dirent* dp; + while ((dp = readdir(dh)) != NULL) { + if (hide(dp->d_name)) { + continue; + } + closedir(dh); + return 1; + } + + closedir(dh); + return 0; +} diff --git a/workspace/all/common/minui_file_utils.h b/workspace/all/common/minui_file_utils.h new file mode 100644 index 00000000..35e6f85c --- /dev/null +++ b/workspace/all/common/minui_file_utils.h @@ -0,0 +1,80 @@ +/** + * minui_file_utils.h - File and directory checking utilities for MinUI + * + * Provides functions to check for specific files and directories related to ROM management: + * - Emulator availability (.pak files) + * - CUE sheets for disc-based games + * - M3U playlists for multi-disc games + * - Directory content checking (non-hidden files) + * + * Extracted from minui.c for testability with file mocking. + */ + +#ifndef __MINUI_FILE_UTILS_H__ +#define __MINUI_FILE_UTILS_H__ + +/** + * Checks if an emulator exists for a given system. + * + * Searches for emulator .pak directories in two locations: + * 1. {paks_path}/Emus/{emu_name}.pak/launch.sh + * 2. {sdcard_path}/Emus/{platform}/{emu_name}.pak/launch.sh + * + * Example: + * hasEmu("gpsp", "/mnt/SDCARD/Paks", "/mnt/SDCARD", "miyoomini") + * Looks for: + * /mnt/SDCARD/Paks/Emus/gpsp.pak/launch.sh + * /mnt/SDCARD/Emus/miyoomini/gpsp.pak/launch.sh + * + * @param emu_name Emulator name (e.g., "gpsp", "gambatte") + * @param paks_path PAKS_PATH constant + * @param sdcard_path SDCARD_PATH constant + * @param platform PLATFORM constant + * @return 1 if emulator exists, 0 otherwise + */ +int MinUI_hasEmu(char* emu_name, const char* paks_path, const char* sdcard_path, + const char* platform); + +/** + * Checks if a directory contains a .cue file for disc-based games. + * + * The .cue file must be named after the directory itself. + * Example: /Roms/PS1/Final Fantasy VII/Final Fantasy VII.cue + * + * @param dir_path Full path to directory + * @param cue_path Output buffer for .cue file path (min 256 bytes) + * @return 1 if .cue file exists, 0 otherwise + * + * @note cue_path is always written, even if file doesn't exist + */ +int MinUI_hasCue(char* dir_path, char* cue_path); + +/** + * Checks if a ROM has an associated .m3u playlist for multi-disc games. + * + * The .m3u file must be in the parent directory and named after that directory. + * Example: For /Roms/PS1/Game/disc1.bin, looks for /Roms/PS1/Game.m3u + * + * @param rom_path Full path to ROM file + * @param m3u_path Output buffer for .m3u file path (min 256 bytes) + * @return 1 if .m3u file exists, 0 otherwise + * + * @note m3u_path is always written, even if file doesn't exist + */ +int MinUI_hasM3u(char* rom_path, char* m3u_path); + +/** + * Checks if a directory contains any non-hidden files. + * + * Hidden files/directories start with '.' or are named ".." or ".". + * + * @param dir_path Full path to directory + * @return 1 if directory exists and contains non-hidden files, 0 otherwise + * + * Example: + * MinUI_hasNonHiddenFiles("/mnt/SDCARD/.minui/Collections") → 1 if has .txt files + * MinUI_hasNonHiddenFiles("/empty/dir") → 0 + */ +int MinUI_hasNonHiddenFiles(const char* dir_path); + +#endif // __MINUI_FILE_UTILS_H__ diff --git a/workspace/all/common/minui_utils.c b/workspace/all/common/minui_utils.c new file mode 100644 index 00000000..84fd1b7e --- /dev/null +++ b/workspace/all/common/minui_utils.c @@ -0,0 +1,48 @@ +/** + * minui_utils.c - Helper utilities for MinUI launcher + * + * Pure logic functions extracted from minui.c. + */ + +#include "minui_utils.h" +#include "utils.h" +#include +#include + +/** + * Gets alphabetical index character for a string. + * + * Returns index 1-26 for strings starting with a-z (case-insensitive). + * Returns 0 for strings starting with non-letters. + * + * @param str String to get index for + * @return Index 0-26 (0=non-letter, 1=A, 2=B, ..., 26=Z) + */ +int MinUI_getIndexChar(char* str) { + char i = 0; + char c = tolower(str[0]); + if (c >= 'a' && c <= 'z') + i = (c - 'a') + 1; + return i; +} + +/** + * Checks if a path is a top-level console directory. + * + * A console directory is one whose parent equals roms_path. + * + * @param path Path to check + * @param roms_path The ROMS_PATH constant (e.g., "/mnt/SDCARD/Roms") + * @return 1 if path is a console directory, 0 otherwise + */ +int MinUI_isConsoleDir(char* path, const char* roms_path) { + char* tmp; + char parent_dir[256]; + strcpy(parent_dir, path); + tmp = strrchr(parent_dir, '/'); + if (!tmp) + return 0; + tmp[0] = '\0'; + + return exactMatch(parent_dir, roms_path); +} diff --git a/workspace/all/common/minui_utils.h b/workspace/all/common/minui_utils.h new file mode 100644 index 00000000..22abb931 --- /dev/null +++ b/workspace/all/common/minui_utils.h @@ -0,0 +1,45 @@ +/** + * minui_utils.h - Helper utilities for MinUI launcher + * + * Pure logic functions extracted from minui.c for testability: + * - Alphabetical indexing + * - Path classification + * + * Extracted from minui.c for unit testing. + */ + +#ifndef __MINUI_UTILS_H__ +#define __MINUI_UTILS_H__ + +/** + * Gets alphabetical index character for a string. + * + * Returns index 1-26 for strings starting with a-z (case-insensitive). + * Returns 0 for strings starting with non-letters. + * + * Used for L1/R1 quick navigation in file browser. + * + * Example: + * "Apple" -> 1 (A) + * "Zelda" -> 26 (Z) + * "007 GoldenEye" -> 0 (non-letter) + * + * @param str String to get index for + * @return Index 0-26 (0=non-letter, 1=A, 2=B, ..., 26=Z) + */ +int MinUI_getIndexChar(char* str); + +/** + * Checks if a path is a top-level console directory. + * + * A console directory is one whose parent is ROMS_PATH. + * Example: "/mnt/SDCARD/Roms/GB" -> true (parent is /mnt/SDCARD/Roms) + * "/mnt/SDCARD/Roms/GB/Homebrew" -> false (parent is .../Roms/GB) + * + * @param path Path to check + * @param roms_path The ROMS_PATH constant (e.g., "/mnt/SDCARD/Roms") + * @return 1 if path is a console directory, 0 otherwise + */ +int MinUI_isConsoleDir(char* path, const char* roms_path); + +#endif // __MINUI_UTILS_H__ diff --git a/workspace/all/common/pad.c b/workspace/all/common/pad.c new file mode 100644 index 00000000..33157109 --- /dev/null +++ b/workspace/all/common/pad.c @@ -0,0 +1,183 @@ +/** + * pad.c - Button and analog stick input handling + * + * Pure logic layer for input state management. No SDL dependencies. + * This module is separated from api.c for testability. + * + * Responsibilities: + * - Analog stick to digital button conversion (with deadzone) + * - Button state tracking (pressed, released, repeated) + * - Menu tap detection (quick tap vs hold) + */ + +#include "pad.h" + +/////////////////////////////// +// Input - Button and analog stick handling +/////////////////////////////// + +// Global input state, polled each frame +PAD_Context pad; + +/** + * Processes analog stick movement and updates button state. + * + * Converts analog axis value to digital button presses (up/down/left/right). + * Handles deadzone, button repeat, and opposite direction cancellation. + * + * @param neg_id Button ID for negative direction (left/up) + * @param pos_id Button ID for positive direction (right/down) + * @param value Analog axis value (-32768 to 32767) + * @param repeat_at Timestamp when button should start repeating + * + * @note Called internally by PLAT_pollInput for analog stick axes + */ +void PAD_setAnalog(int neg_id, int pos_id, int value, int repeat_at) { + // LOG_info("neg %i pos %i value %i\n", neg_id, pos_id, value); + int neg = 1 << neg_id; + int pos = 1 << pos_id; + if (value > AXIS_DEADZONE) { // pressing + if (!(pad.is_pressed & pos)) { // not pressing + pad.is_pressed |= pos; // set + pad.just_pressed |= pos; // set + pad.just_repeated |= pos; // set + pad.repeat_at[pos_id] = repeat_at; + + if (pad.is_pressed & neg) { // was pressing opposite + pad.is_pressed &= ~neg; // unset + pad.just_repeated &= ~neg; // unset + pad.just_released |= neg; // set + } + } + } else if (value < -AXIS_DEADZONE) { // pressing + if (!(pad.is_pressed & neg)) { // not pressing + pad.is_pressed |= neg; // set + pad.just_pressed |= neg; // set + pad.just_repeated |= neg; // set + pad.repeat_at[neg_id] = repeat_at; + + if (pad.is_pressed & pos) { // was pressing opposite + pad.is_pressed &= ~pos; // unset + pad.just_repeated &= ~pos; // unset + pad.just_released |= pos; // set + } + } + } else { // not pressing + if (pad.is_pressed & neg) { // was pressing + pad.is_pressed &= ~neg; // unset + pad.just_repeated &= ~neg; // unset + pad.just_released |= neg; // set + } + if (pad.is_pressed & pos) { // was pressing + pad.is_pressed &= ~pos; // unset + pad.just_repeated &= ~pos; // unset + pad.just_released |= pos; // set + } + } +} + +/** + * Resets all button states to unpressed. + * + * Clears all button press/release/repeat flags. + * Call this when changing contexts (e.g., entering/exiting sleep). + */ +void PAD_reset(void) { + // LOG_info("PAD_reset"); + pad.just_pressed = BTN_NONE; + pad.is_pressed = BTN_NONE; + pad.just_released = BTN_NONE; + pad.just_repeated = BTN_NONE; +} + +/** + * Checks if any button was just pressed this frame. + * + * @return 1 if any button was just pressed, 0 otherwise + */ +int PAD_anyJustPressed(void) { + return pad.just_pressed != BTN_NONE; +} + +/** + * Checks if any button is currently held down. + * + * @return 1 if any button is pressed, 0 otherwise + */ +int PAD_anyPressed(void) { + return pad.is_pressed != BTN_NONE; +} + +/** + * Checks if any button was just released this frame. + * + * @return 1 if any button was just released, 0 otherwise + */ +int PAD_anyJustReleased(void) { + return pad.just_released != BTN_NONE; +} + +/** + * Checks if a specific button was just pressed this frame. + * + * @param btn Button bitmask (e.g., BTN_A, BTN_START) + * @return 1 if button was just pressed, 0 otherwise + */ +int PAD_justPressed(int btn) { + return pad.just_pressed & btn; +} + +/** + * Checks if a specific button is currently held down. + * + * @param btn Button bitmask (e.g., BTN_A, BTN_START) + * @return 1 if button is pressed, 0 otherwise + */ +int PAD_isPressed(int btn) { + return pad.is_pressed & btn; +} + +/** + * Checks if a specific button was just released this frame. + * + * @param btn Button bitmask (e.g., BTN_A, BTN_START) + * @return 1 if button was just released, 0 otherwise + */ +int PAD_justReleased(int btn) { + return pad.just_released & btn; +} + +/** + * Checks if a specific button is repeating (held for repeat interval). + * + * @param btn Button bitmask (e.g., BTN_DPAD_UP) + * @return 1 if button is repeating this frame, 0 otherwise + */ +int PAD_justRepeated(int btn) { + return pad.just_repeated & btn; +} + +/** + * Detects a quick tap of the menu button. + * + * Returns true if menu button was pressed and released within MENU_DELAY (250ms) + * without any brightness adjustment (PLUS/MINUS) being triggered. + * + * @param now Current timestamp in milliseconds + * @return 1 if menu was tapped (not held), 0 otherwise + * + * @note Used to distinguish menu tap from menu+brightness adjustment + */ +int PAD_tappedMenu(uint32_t now) { +#define MENU_DELAY 250 // also in PWR_update() + static uint32_t menu_start = 0; + static int ignore_menu = 0; + if (PAD_justPressed(BTN_MENU)) { + ignore_menu = 0; + menu_start = now; + } else if (PAD_isPressed(BTN_MENU) && BTN_MOD_BRIGHTNESS == BTN_MENU && + (PAD_justPressed(BTN_MOD_PLUS) || PAD_justPressed(BTN_MOD_MINUS))) { + ignore_menu = 1; + } + return (!ignore_menu && PAD_justReleased(BTN_MENU) && now - menu_start < MENU_DELAY); +} diff --git a/workspace/all/common/pad.h b/workspace/all/common/pad.h new file mode 100644 index 00000000..e0c641be --- /dev/null +++ b/workspace/all/common/pad.h @@ -0,0 +1,144 @@ +/** + * pad.h - Button and analog stick input handling + * + * Pure logic layer for input state management. No SDL dependencies. + * This module is separated from api.c for testability. + * + * Responsibilities: + * - Analog stick to digital button conversion (with deadzone) + * - Button state tracking (pressed, released, repeated) + * - Menu tap detection (quick tap vs hold) + */ + +#ifndef __PAD_H__ +#define __PAD_H__ + +#include "defines.h" +#include + +// Type definitions +// When included from api.c, api.h is included first and defines these +// When included standalone (tests, pad.c itself), we need to define them +#ifndef __API_H__ +/** + * Analog stick axis values. + */ +typedef struct PAD_Axis { + int x; // X-axis value (-32768 to 32767) + int y; // Y-axis value (-32768 to 32767) +} PAD_Axis; + +/** + * Input context containing all button state. + */ +typedef struct PAD_Context { + int is_pressed; // Bitmask of currently pressed buttons + int just_pressed; // Bitmask of buttons pressed this frame + int just_released; // Bitmask of buttons released this frame + int just_repeated; // Bitmask of buttons auto-repeated this frame + uint32_t repeat_at[BTN_ID_COUNT]; // Timestamp for next repeat per button + PAD_Axis laxis; // Left analog stick state + PAD_Axis raxis; // Right analog stick state +} PAD_Context; + +/** + * Global input state, polled each frame. + */ +extern PAD_Context pad; +#endif + +/** + * Analog stick deadzone (threshold for registering input). + */ +#define AXIS_DEADZONE 0x4000 + +/** + * Processes analog stick movement and updates button state. + * + * Converts analog axis value to digital button presses (up/down/left/right). + * Handles deadzone, button repeat, and opposite direction cancellation. + * + * @param neg_id Button ID for negative direction (left/up) + * @param pos_id Button ID for positive direction (right/down) + * @param value Analog axis value (-32768 to 32767) + * @param repeat_at Timestamp when button should start repeating + * + * @note Called internally by PLAT_pollInput for analog stick axes + */ +void PAD_setAnalog(int neg_id, int pos_id, int value, int repeat_at); + +/** + * Resets all button states to unpressed. + * + * Clears all button press/release/repeat flags. + * Call this when changing contexts (e.g., entering/exiting sleep). + */ +void PAD_reset(void); + +/** + * Checks if any button was just pressed this frame. + * + * @return 1 if any button was just pressed, 0 otherwise + */ +int PAD_anyJustPressed(void); + +/** + * Checks if any button is currently held down. + * + * @return 1 if any button is pressed, 0 otherwise + */ +int PAD_anyPressed(void); + +/** + * Checks if any button was just released this frame. + * + * @return 1 if any button was just released, 0 otherwise + */ +int PAD_anyJustReleased(void); + +/** + * Checks if a specific button was just pressed this frame. + * + * @param btn Button bitmask (e.g., BTN_A, BTN_START) + * @return 1 if button was just pressed, 0 otherwise + */ +int PAD_justPressed(int btn); + +/** + * Checks if a specific button is currently held down. + * + * @param btn Button bitmask (e.g., BTN_A, BTN_START) + * @return 1 if button is pressed, 0 otherwise + */ +int PAD_isPressed(int btn); + +/** + * Checks if a specific button was just released this frame. + * + * @param btn Button bitmask (e.g., BTN_A, BTN_START) + * @return 1 if button was just released, 0 otherwise + */ +int PAD_justReleased(int btn); + +/** + * Checks if a specific button is repeating (held for repeat interval). + * + * @param btn Button bitmask (e.g., BTN_DPAD_UP) + * @return 1 if button is repeating this frame, 0 otherwise + */ +int PAD_justRepeated(int btn); + +/** + * Detects a quick tap of the menu button. + * + * Returns true if menu button was pressed and released within MENU_DELAY (250ms) + * without any brightness adjustment (PLUS/MINUS) being triggered. + * + * @param now Current timestamp in milliseconds + * @return 1 if menu was tapped (not held), 0 otherwise + * + * @note Used to distinguish menu tap from menu+brightness adjustment + */ +int PAD_tappedMenu(uint32_t now); + +#endif // __PAD_H__ diff --git a/workspace/all/common/recent_file.c b/workspace/all/common/recent_file.c new file mode 100644 index 00000000..961a9837 --- /dev/null +++ b/workspace/all/common/recent_file.c @@ -0,0 +1,106 @@ +/** + * recent_file.c - Recently played games file I/O + * + * Extracted from minui.c for testability. + */ + +#define _POSIX_C_SOURCE 200809L // Required for strdup() + +#include "recent_file.h" +#include "utils.h" +#include +#include +#include + +/** + * Parses recent.txt and returns all valid entries. + * + * Reads the recent.txt file, validates each ROM exists, and creates + * entries for valid ROMs only. + * + * Format: pathalias (alias is optional) + * + * @param recent_path Full path to recent.txt file + * @param sdcard_path SDCARD_PATH constant + * @param entry_count Output: number of entries found + * @return Array of Recent_Entry* (caller must free with Recent_freeEntries) + */ +Recent_Entry** Recent_parse(char* recent_path, const char* sdcard_path, int* entry_count) { + *entry_count = 0; + + // Allocate space for up to 50 recents + Recent_Entry** entries = malloc(sizeof(Recent_Entry*) * 50); + + FILE* file = fopen(recent_path, "r"); + if (file) { + char line[256]; + while (fgets(line, 256, file) != NULL && *entry_count < 50) { + normalizeNewline(line); + trimTrailingNewlines(line); + if (strlen(line) == 0) + continue; // skip empty lines + + // Parse tab-delimited format: pathalias + char* path = line; + char* alias = NULL; + char* tmp = strchr(line, '\t'); + if (tmp) { + tmp[0] = '\0'; + alias = tmp + 1; + } + + // Construct full SD card path + char sd_path[256]; + sprintf(sd_path, "%s%s", sdcard_path, path); + + // Only include ROMs that exist + if (exists(sd_path)) { + Recent_Entry* entry = malloc(sizeof(Recent_Entry)); + entry->path = strdup(path); // Store relative path + entry->alias = alias ? strdup(alias) : NULL; + + entries[*entry_count] = entry; + (*entry_count)++; + } + } + fclose(file); + } + + return entries; +} + +/** + * Saves recent entries to recent.txt file + */ +int Recent_save(char* recent_path, Recent_Entry** entries, int count) { + FILE* file = fopen(recent_path, "w"); + if (!file) { + return 0; + } + + for (int i = 0; i < count; i++) { + Recent_Entry* entry = entries[i]; + fputs(entry->path, file); + if (entry->alias) { + fputs("\t", file); + fputs(entry->alias, file); + } + putc('\n', file); + } + + fclose(file); + return 1; +} + +/** + * Frees entry array returned by Recent_parse() + */ +void Recent_freeEntries(Recent_Entry** entries, int count) { + for (int i = 0; i < count; i++) { + free(entries[i]->path); + if (entries[i]->alias) + free(entries[i]->alias); + free(entries[i]); + } + free(entries); +} diff --git a/workspace/all/common/recent_file.h b/workspace/all/common/recent_file.h new file mode 100644 index 00000000..32b7cd6c --- /dev/null +++ b/workspace/all/common/recent_file.h @@ -0,0 +1,66 @@ +/** + * recent_file.h - Recently played games file I/O + * + * Manages recent.txt which stores recently played ROMs. + * Format: Tab-delimited path and optional alias + * /Roms/GB/mario.gbSuper Mario + * /Roms/NES/zelda.nes + * + * Provides both read (Recent_parse) and write (Recent_save) operations. + * Extracted from minui.c for testability. + */ + +#ifndef __RECENT_FILE_H__ +#define __RECENT_FILE_H__ + +/** + * Recent game entry + */ +typedef struct Recent_Entry { + char* path; // ROM path (relative to SDCARD_PATH, starts with /) + char* alias; // Custom display name (NULL if no alias) +} Recent_Entry; + +/** + * Parses recent.txt and returns all valid entries. + * + * Reads the recent.txt file, validates each ROM exists, and creates + * entries for valid ROMs only. + * + * Format: pathalias (alias is optional) + * Example: + * /Roms/GB/mario.gbSuper Mario Land + * /Roms/NES/zelda.nes + * + * @param recent_path Full path to recent.txt file + * @param sdcard_path SDCARD_PATH constant (e.g., "/mnt/SDCARD") + * @param entry_count Output: number of entries found + * @return Array of Recent_Entry* (caller must free with Recent_freeEntries) + * + * @note Only includes ROMs that exist on filesystem + * @note Skips empty lines + * @note Paths in recent.txt are relative to sdcard_path + */ +Recent_Entry** Recent_parse(char* recent_path, const char* sdcard_path, int* entry_count); + +/** + * Saves recent entries to recent.txt file + * + * Writes entries in tab-delimited format: pathalias (alias optional) + * + * @param recent_path Full path to recent.txt file + * @param entries Array of entry pointers to save + * @param count Number of entries in array + * @return 1 on success, 0 on failure (couldn't open file) + */ +int Recent_save(char* recent_path, Recent_Entry** entries, int count); + +/** + * Frees entry array returned by Recent_parse() + * + * @param entries Array of entry pointers + * @param count Number of entries in array + */ +void Recent_freeEntries(Recent_Entry** entries, int count); + +#endif // __RECENT_FILE_H__ diff --git a/workspace/all/minarch/makefile b/workspace/all/minarch/makefile index 3b2b020c..fe0bacf5 100644 --- a/workspace/all/minarch/makefile +++ b/workspace/all/minarch/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = minarch INCDIR = -I. -I./libretro-common/include/ -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/api.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/api.c ../common/collections.c ../common/pad.c ../common/gfx_text.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS = $(ARCH) -fomit-frame-pointer diff --git a/workspace/all/minput/makefile b/workspace/all/minput/makefile index ce79a58c..b299bbc2 100644 --- a/workspace/all/minput/makefile +++ b/workspace/all/minput/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = minput INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/collections.c ../common/pad.c ../common/gfx_text.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS = $(ARCH) -fomit-frame-pointer diff --git a/workspace/all/minui/makefile b/workspace/all/minui/makefile index ead5e4b3..597a3595 100644 --- a/workspace/all/minui/makefile +++ b/workspace/all/minui/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = minui INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/api.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/api.c ../common/collections.c ../common/pad.c ../common/gfx_text.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS = $(ARCH) -fomit-frame-pointer diff --git a/workspace/all/minui/minui.c b/workspace/all/minui/minui.c index 9a8df15a..bea48361 100644 --- a/workspace/all/minui/minui.c +++ b/workspace/all/minui/minui.c @@ -38,214 +38,15 @@ #include #include "api.h" +#include "collections.h" #include "defines.h" #include "utils.h" /////////////////////////////// -// Dynamic array implementation +// Note: Array and Hash data structures moved to workspace/all/common/collections.c +// This makes them reusable across all components and easier to test. /////////////////////////////// -/** - * Generic dynamic array with automatic growth. - * - * Stores pointers to any type. Initial capacity is 8, - * doubles when full. Used for directories, entries, and recents. - */ -typedef struct Array { - int count; - int capacity; - void** items; -} Array; - -/** - * Creates a new empty array. - * - * @return Pointer to allocated Array - * - * @warning Caller must free with Array_free() or type-specific free function - */ -static Array* Array_new(void) { - Array* self = malloc(sizeof(Array)); - self->count = 0; - self->capacity = 8; - self->items = malloc(sizeof(void*) * self->capacity); - return self; -} - -/** - * Appends an item to the end of the array. - * - * Automatically doubles capacity when full. - * - * @param self Array to modify - * @param item Pointer to add (not copied, caller retains ownership) - */ -static void Array_push(Array* self, void* item) { - if (self->count >= self->capacity) { - self->capacity *= 2; - self->items = realloc(self->items, sizeof(void*) * self->capacity); - } - self->items[self->count++] = item; -} - -/** - * Inserts an item at the beginning of the array. - * - * Shifts all existing items to the right. Used to add most - * recent game to top of recents list. - * - * @param self Array to modify - * @param item Pointer to insert - */ -static void Array_unshift(Array* self, void* item) { - if (self->count == 0) - return Array_push(self, item); - Array_push(self, NULL); // ensures we have enough capacity - for (int i = self->count - 2; i >= 0; i--) { - self->items[i + 1] = self->items[i]; - } - self->items[0] = item; -} - -/** - * Removes and returns the last item from the array. - * - * @param self Array to modify - * @return Pointer to removed item, or NULL if array is empty - * - * @note Caller assumes ownership of returned pointer - */ -static void* Array_pop(Array* self) { - if (self->count == 0) - return NULL; - return self->items[--self->count]; -} - -/** - * Reverses the order of all items in the array. - * - * @param self Array to modify - */ -static void Array_reverse(Array* self) { - int end = self->count - 1; - int mid = self->count / 2; - for (int i = 0; i < mid; i++) { - void* item = self->items[i]; - self->items[i] = self->items[end - i]; - self->items[end - i] = item; - } -} - -/** - * Frees the array structure. - * - * @param self Array to free - * - * @warning Does NOT free the items themselves - use type-specific free functions - */ -static void Array_free(Array* self) { - free(self->items); - free(self); -} - -/** - * Finds the index of a string in a string array. - * - * @param self Array of string pointers - * @param str String to find - * @return Index of first matching string, or -1 if not found - */ -static int StringArray_indexOf(Array* self, char* str) { - for (int i = 0; i < self->count; i++) { - if (exactMatch(self->items[i], str)) - return i; - } - return -1; -} - -/** - * Frees a string array and all strings it contains. - * - * @param self Array to free - */ -static void StringArray_free(Array* self) { - for (int i = 0; i < self->count; i++) { - free(self->items[i]); - } - Array_free(self); -} - -/////////////////////////////// -// Simple hash map (key-value store) -/////////////////////////////// - -/** - * Simple key-value map using parallel arrays. - * - * Used for loading map.txt files that alias ROM display names. - * Not a true hash - just linear search through keys array. - */ -typedef struct Hash { - Array* keys; - Array* values; -} Hash; // not really a hash - -/** - * Creates a new empty hash map. - * - * @return Pointer to allocated Hash - * - * @warning Caller must free with Hash_free() - */ -static Hash* Hash_new(void) { - Hash* self = malloc(sizeof(Hash)); - self->keys = Array_new(); - self->values = Array_new(); - return self; -} - -/** - * Frees a hash map and all its keys and values. - * - * @param self Hash to free - */ -static void Hash_free(Hash* self) { - StringArray_free(self->keys); - StringArray_free(self->values); - free(self); -} - -/** - * Stores a key-value pair in the hash map. - * - * Both key and value are duplicated with strdup(). - * Does not check for duplicate keys - allows multiple entries. - * - * @param self Hash to modify - * @param key Key string - * @param value Value string - */ -static void Hash_set(Hash* self, char* key, char* value) { - Array_push(self->keys, strdup(key)); - Array_push(self->values, strdup(value)); -} - -/** - * Retrieves a value by key from the hash map. - * - * @param self Hash to search - * @param key Key to look up - * @return Pointer to value string, or NULL if key not found - * - * @note Returned pointer is owned by the Hash - do not free - */ -static char* Hash_get(Hash* self, char* key) { - int i = StringArray_indexOf(self->keys, key); - if (i == -1) - return NULL; - return self->values->items[i]; -} - /////////////////////////////// // File browser entries /////////////////////////////// diff --git a/workspace/all/say/makefile b/workspace/all/say/makefile index de46bff0..0f35d709 100644 --- a/workspace/all/say/makefile +++ b/workspace/all/say/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = say INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/collections.c ../common/pad.c ../common/gfx_text.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS = $(ARCH) -fomit-frame-pointer