diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ca84cf9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Enforce Unix line endings for shell scripts +*.sh text eol=lf \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index cd29f31..51d4385 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -20,6 +20,8 @@ jobs: docker build . -t tmp docker build . -f Dockerfile.buildlocal -t builder docker run --rm -v ${PWD}:/project builder make + docker run --rm -v ${PWD}:/project builder /bin/bash -c "make install && chmod +x tests/parameter_checker/runner.sh && cd tests/parameter_checker && ./runner.sh" + docker run --rm -v ${PWD}:/project builder /bin/bash -c "make install && chmod +x tests/parameter_checker/negative_runner.sh && cd tests/parameter_checker && ./negative_runner.sh" - uses: actions/upload-artifact@master with: name: lib diff --git a/.gitignore b/.gitignore index 163041f..36beb40 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ CMakeLists.txt .idea/ cmake-build-debug/ share/libfunctionpatcher.ld +*.elf +*.rpx +*.wuhb +tests/parameter_checker/build/ diff --git a/Dockerfile b/Dockerfile index 7afb775..c31d7d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/wiiu-env/devkitppc:20240423 +FROM ghcr.io/wiiu-env/devkitppc:20250608 WORKDIR tmp_build COPY . . diff --git a/Dockerfile.buildlocal b/Dockerfile.buildlocal index bb8c5c8..01eb00c 100644 --- a/Dockerfile.buildlocal +++ b/Dockerfile.buildlocal @@ -1,3 +1,3 @@ -FROM ghcr.io/wiiu-env/devkitppc:20240423 +FROM ghcr.io/wiiu-env/devkitppc:20250608 WORKDIR project \ No newline at end of file diff --git a/include/notifications/notifications.h b/include/notifications/notifications.h index 8dd0a8f..f08f9e5 100644 --- a/include/notifications/notifications.h +++ b/include/notifications/notifications.h @@ -374,23 +374,20 @@ NotificationModuleStatus NotificationModule_FinishDynamicNotificationWithShake(N float durationBeforeFadeOutInSeconds, float shakeDuration); -// Copy pasted from libcurl... -/* the typechecker doesn't work in C++ (yet) */ -#if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ - ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) && \ - !defined(__cplusplus) -#include "typecheck-gcc.h" +#ifdef __cplusplus +} +#endif + +// Idea taken from libcurl +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ + ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +#include "typechecks-gcc.h" #else #if defined(__STDC__) && (__STDC__ >= 1) /* This preprocessor magic that replaces a call with the exact same call is - only done to make sure application authors pass exactly three arguments - to these functions. */ + only done to make sure application authors pass exactly three arguments + to these functions. */ #define NotificationModule_SetDefaultValue(type, valueType, param) NotificationModule_SetDefaultValue(type, valueType, param) #endif /* __STDC__ >= 1 */ -#endif /* gcc >= 4.3 && !__cplusplus */ - - -#ifdef __cplusplus -} -#endif +#endif /* gcc >= 4.3 */ diff --git a/include/notifications/typechecks-gcc.h b/include/notifications/typechecks-gcc.h new file mode 100644 index 0000000..e0a914b --- /dev/null +++ b/include/notifications/typechecks-gcc.h @@ -0,0 +1,172 @@ +#ifndef NOTIFICATIONS_TYPECHECK_GCC_H +#define NOTIFICATIONS_TYPECHECK_GCC_H + +#ifdef __cplusplus +#include /* For std::nullptr_t */ +#else +#include /* For NULL */ +#endif + +/* * Warning generators (Common to C and C++) + * These define static functions that trigger a compiler warning when called. + */ +#define _NM_WARNING(id, message) \ + static void __attribute__((__warning__(message))) \ + __attribute__((__unused__)) __attribute__((__noinline__)) \ + id(void) { __asm__(""); } + +#ifdef __cplusplus +extern "C" { +#endif + +_NM_WARNING(_nm_warn_NMColor, "NotificationModule_SetDefaultValue expects 'NMColor' for this option.") +_NM_WARNING(_nm_warn_float, "NotificationModule_SetDefaultValue expects 'float' or 'double' for this option.") +_NM_WARNING(_nm_warn_callback, "NotificationModule_SetDefaultValue expects 'NotificationModuleNotificationFinishedCallback' for this option.") +_NM_WARNING(_nm_warn_context, "NotificationModule_SetDefaultValue expects 'void*' for this option.") +_NM_WARNING(_nm_warn_bool, "NotificationModule_SetDefaultValue expects 'bool' (or 'int') for this option.") + +#ifdef __cplusplus +} +#endif + + +/* ========================================================================= + * IMPLEMENTATION SELECTION + * ========================================================================= */ + +#if defined(__cplusplus) + +/* ========================================== + * C++17 Implementation + * Only active if -std=c++17 or higher is used. + * ========================================== */ +#if __cplusplus >= 201703L + +namespace NM_Check { + /* NMColor Checker */ + inline bool check_NMColor(NMColor) { return true; } + inline bool check_NMColor(void *) { return false; } /* Sink for NULL */ + template + inline bool check_NMColor(T) { return false; } + + /* Float Checker (accepts float/double) */ + inline bool check_float(float) { return true; } + inline bool check_float(double) { return true; } + inline bool check_float(void *) { return false; } /* Sink for NULL */ + template + inline bool check_float(T) { return false; } + + /* Bool Checker (accepts bool/int) */ + inline bool check_bool(bool) { return true; } + inline bool check_bool(int) { return true; } + inline bool check_bool(void *) { return false; } /* Sink for NULL */ + template + inline bool check_bool(T) { return false; } + + /* Callback Checker */ + inline bool check_callback(NotificationModuleNotificationFinishedCallback) { return true; } + /* Explicit void* (e.g. casting) */ + inline bool check_callback(void *) { return true; } + inline bool check_callback(int i) { return i == 0; } + inline bool check_callback(long i) { return i == 0; } + inline bool check_callback(std::nullptr_t) { return true; } + template + inline bool check_callback(T) { return false; } + + /* Context Checker */ + inline bool check_context(void *) { return true; } + inline bool check_context(int i) { return i == 0; } + inline bool check_context(long i) { return i == 0; } + inline bool check_context(std::nullptr_t) { return true; } + template + inline bool check_context(T) { return false; } +} // namespace NM_Check + +/* Macros mapping to C++ namespace calls */ +#define _nm_is_NMColor(x) NM_Check::check_NMColor(x) +#define _nm_is_float(x) NM_Check::check_float(x) +#define _nm_is_bool(x) NM_Check::check_bool(x) +#define _nm_is_callback(x) NM_Check::check_callback(x) +#define _nm_is_context(x) NM_Check::check_context(x) + +#else +/* ========================================== + * Pre-C++17 Implementation + * Checks are DISABLED to avoid errors in older versions. + * ========================================== */ +#define _nm_is_NMColor(x) (1) +#define _nm_is_float(x) (1) +#define _nm_is_bool(x) (1) +#define _nm_is_callback(x) (1) +#define _nm_is_context(x) (1) +#endif + +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + +/* ========================================== + * C11 Implementation (using _Generic) + * ========================================== */ + +#define _nm_is_NMColor(x) _Generic((x), \ + NMColor : 1, \ + default : 0) + +#define _nm_is_float(x) _Generic((x), \ + float : 1, \ + double : 1, \ + default : 0) + +#define _nm_is_bool(x) _Generic((x), \ + _Bool : 1, \ + int : 1, \ + default : 0) + +#define _nm_is_callback(x) _Generic((x), \ + NotificationModuleNotificationFinishedCallback : 1, \ + void * : 1, \ + default : 0) + +#define _nm_is_context(x) _Generic((x), \ + void * : 1, \ + default : 0) + +#else + +/* ========================================== + * Legacy C Implementation + * Partial checking only. + * ========================================== */ + +#define _nm_is_type(x, type) __builtin_types_compatible_p(__typeof__(x), type) + +/* Disable complex checks in old C */ +#define _nm_is_NMColor(x) (1) +#define _nm_is_callback(x) (1) +#define _nm_is_context(x) (1) + +/* Scalars are safe to check */ +#define _nm_is_float(x) (_nm_is_type(x, float) || _nm_is_type(x, double)) +#define _nm_is_bool(x) (_nm_is_type(x, int) || _nm_is_type(x, _Bool)) + +#endif + +#define NotificationModule_SetDefaultValue(type, option, value) \ + __extension__({ \ + if (__builtin_constant_p(option)) { \ + if ((option == NOTIFICATION_MODULE_DEFAULT_OPTION_BACKGROUND_COLOR) && !_nm_is_NMColor(value)) \ + _nm_warn_NMColor(); \ + else if ((option == NOTIFICATION_MODULE_DEFAULT_OPTION_TEXT_COLOR) && !_nm_is_NMColor(value)) \ + _nm_warn_NMColor(); \ + else if ((option == NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT) && !_nm_is_float(value)) \ + _nm_warn_float(); \ + else if ((option == NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION) && !_nm_is_callback(value)) \ + _nm_warn_callback(); \ + else if ((option == NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION_CONTEXT) && !_nm_is_context(value)) \ + _nm_warn_context(); \ + else if ((option == NOTIFICATION_MODULE_DEFAULT_OPTION_KEEP_UNTIL_SHOWN) && !_nm_is_bool(value)) \ + _nm_warn_bool(); \ + } \ + (NotificationModule_SetDefaultValue)(type, option, value); \ + }) + +#endif /* NOTIFICATIONS_TYPECHECK_GCC_H */ \ No newline at end of file diff --git a/tests/parameter_checker/Makefile b/tests/parameter_checker/Makefile new file mode 100644 index 0000000..4c78162 --- /dev/null +++ b/tests/parameter_checker/Makefile @@ -0,0 +1,184 @@ +#------------------------------------------------------------------------------- +.SUFFIXES: +#------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) + +#------------------------------------------------------------------------------- +# APP_NAME sets the long name of the application +# APP_SHORTNAME sets the short name of the application +# APP_AUTHOR sets the author of the application +#------------------------------------------------------------------------------- +#APP_NAME := Application Name +#APP_SHORTNAME := App Name +#APP_AUTHOR := Built with devkitPPC & wut + +include $(DEVKITPRO)/wut/share/wut_rules + +WUMS_ROOT := $(DEVKITPRO)/wums + +#------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# CONTENT is the path to the bundled folder that will be mounted as /vol/content/ +# ICON is the game icon, leave blank to use default rule +# TV_SPLASH is the image displayed during bootup on the TV, leave blank to use default rule +# DRC_SPLASH is the image displayed during bootup on the DRC, leave blank to use default rule +#------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +# Default to src_cpp if not specified +TEST_SRC ?= src_cpp +SOURCES := $(TEST_SRC) +DATA := data +INCLUDES := $(TEST_SRC) +CONTENT := +ICON := +TV_SPLASH := +DRC_SPLASH := + +#------------------------------------------------------------------------------- +# options for code generation +#------------------------------------------------------------------------------- +CFLAGS := -g -Wall -Werror -O2 -ffunction-sections \ + $(MACHDEP) $(USER_CFLAGS) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ + +CXXFLAGS := $(CFLAGS) $(USER_CXXFLAGS) + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) + +LIBS := -lwut -lnotifications -lstdc++ + +#------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level +# containing include and lib +#------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUMS_ROOT) + +#------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#------------------------------------------------------------------------------- + export LD := $(CC) +#------------------------------------------------------------------------------- +else +#------------------------------------------------------------------------------- + export LD := $(CXX) +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifneq (,$(strip $(CONTENT))) + export APP_CONTENT := $(TOPDIR)/$(CONTENT) +endif + +ifneq (,$(strip $(ICON))) + export APP_ICON := $(TOPDIR)/$(ICON) +else ifneq (,$(wildcard $(TOPDIR)/$(TARGET).png)) + export APP_ICON := $(TOPDIR)/$(TARGET).png +else ifneq (,$(wildcard $(TOPDIR)/icon.png)) + export APP_ICON := $(TOPDIR)/icon.png +endif + +ifneq (,$(strip $(TV_SPLASH))) + export APP_TV_SPLASH := $(TOPDIR)/$(TV_SPLASH) +else ifneq (,$(wildcard $(TOPDIR)/tv-splash.png)) + export APP_TV_SPLASH := $(TOPDIR)/tv-splash.png +else ifneq (,$(wildcard $(TOPDIR)/splash.png)) + export APP_TV_SPLASH := $(TOPDIR)/splash.png +endif + +ifneq (,$(strip $(DRC_SPLASH))) + export APP_DRC_SPLASH := $(TOPDIR)/$(DRC_SPLASH) +else ifneq (,$(wildcard $(TOPDIR)/drc-splash.png)) + export APP_DRC_SPLASH := $(TOPDIR)/drc-splash.png +else ifneq (,$(wildcard $(TOPDIR)/splash.png)) + export APP_DRC_SPLASH := $(TOPDIR)/splash.png +endif + +.PHONY: $(BUILD) clean all + +#------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).wuhb $(TARGET).rpx $(TARGET).elf + +#------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#------------------------------------------------------------------------------- +# main targets +#------------------------------------------------------------------------------- +all : $(OUTPUT).wuhb + +$(OUTPUT).wuhb : $(OUTPUT).rpx +$(OUTPUT).rpx : $(OUTPUT).elf +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- diff --git a/tests/parameter_checker/negative_runner.sh b/tests/parameter_checker/negative_runner.sh new file mode 100644 index 0000000..7e0b2a1 --- /dev/null +++ b/tests/parameter_checker/negative_runner.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# Define Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo "========================================" +echo "Starting Advanced Compilation Matrix" +echo "========================================" + +# Track overall status +OVERALL_SUCCESS=true + +# --------------------------------------------------------- +# Helper Function: Run a specific Negative Test +# --------------------------------------------------------- +run_check() { + TEST_NAME=$1 # e.g., TEST_FAIL_COLOR + SRC_DIR=$2 # e.g., src_fail_cpp + STD=$3 # e.g., gnu++17 + TYPE=$4 # CXX or C + + echo -n "Checking $TEST_NAME [$STD]... " + + # 1. POSITIVE VERIFICATION (Must Compile with -DMAKE_VALID) + make clean --no-print-directory > /dev/null 2>&1 + + # Construct Flags + if [ "$TYPE" == "CXX" ]; then + FLAGS="-std=$STD -D$TEST_NAME -DMAKE_VALID" + OUT=$(make TEST_SRC=$SRC_DIR USER_CXXFLAGS="$FLAGS" --no-print-directory 2>&1) + else + FLAGS="-std=$STD -D$TEST_NAME -DMAKE_VALID" + OUT=$(make TEST_SRC=$SRC_DIR USER_CFLAGS="$FLAGS" --no-print-directory 2>&1) + fi + + if [ $? -ne 0 ]; then + echo -e "${RED}[ERROR]${NC}" + echo " -> Valid code failed to build! Setup is broken." + echo "$OUT" + OVERALL_SUCCESS=false + return + fi + + # 2. NEGATIVE VERIFICATION (Must FAIL without -DMAKE_VALID) + make clean --no-print-directory > /dev/null 2>&1 + + if [ "$TYPE" == "CXX" ]; then + FLAGS="-std=$STD -D$TEST_NAME" + OUT=$(make TEST_SRC=$SRC_DIR USER_CXXFLAGS="$FLAGS" --no-print-directory 2>&1) + else + FLAGS="-std=$STD -D$TEST_NAME" + OUT=$(make TEST_SRC=$SRC_DIR USER_CFLAGS="$FLAGS" --no-print-directory 2>&1) + fi + + if [ $? -eq 0 ]; then + echo -e "${RED}[ERROR]${NC}" + echo " -> Invalid code SUCCEEDED! Type check failed to catch error." + echo "make TEST_SRC=$SRC_DIR USER_CFLAGS="$FLAGS" --no-print-directory 2>&1" + OVERALL_SUCCESS=false + return + fi + + echo -e "${GREEN}[OK]${NC}" +} + +# --------------------------------------------------------- +# C++ NEGATIVE TESTS +# --------------------------------------------------------- +# Note: C++ Type checks are only active on C++17 and newer. +CPP_FULL_CHECK_VERSIONS=("gnu++17" "gnu++20" "gnu++23") + +for std in "${CPP_FULL_CHECK_VERSIONS[@]}"; do + run_check "TEST_FAIL_COLOR" "src_fail_cpp" "$std" "CXX" + run_check "TEST_FAIL_DURATION" "src_fail_cpp" "$std" "CXX" + run_check "TEST_FAIL_CALLBACK" "src_fail_cpp" "$std" "CXX" + run_check "TEST_FAIL_CONTEXT" "src_fail_cpp" "$std" "CXX" + run_check "TEST_FAIL_BOOL" "src_fail_cpp" "$std" "CXX" +done + +# --------------------------------------------------------- +# C NEGATIVE TESTS +# --------------------------------------------------------- +# C11 and newer have support for _Generic (Full Checks) +C_FULL_CHECK_VERSIONS=("gnu11" "gnu17" "gnu18" "gnu2x") +# C99 only supports built-in compatibility checks (Scalar only: float, bool) +C_SCALAR_CHECK_VERSIONS=("gnu99") + +# 1. Full Checks (Color, Callback, Context) - C11+ Only +for std in "${C_FULL_CHECK_VERSIONS[@]}"; do + run_check "TEST_FAIL_COLOR" "src_fail_c" "$std" "C" + run_check "TEST_FAIL_CALLBACK" "src_fail_c" "$std" "C" + run_check "TEST_FAIL_CONTEXT" "src_fail_c" "$std" "C" + run_check "TEST_FAIL_DURATION" "src_fail_c" "$std" "C" + run_check "TEST_FAIL_BOOL" "src_fail_c" "$std" "C" +done + +# 2. Scalar Checks (Float, Bool) - C99+ +for std in "${C_SCALAR_CHECK_VERSIONS[@]}"; do + run_check "TEST_FAIL_COLOR" "src_fail_c" "$std" "C" + run_check "TEST_FAIL_DURATION" "src_fail_c" "$std" "C" + run_check "TEST_FAIL_BOOL" "src_fail_c" "$std" "C" +done + +# --------------------------------------------------------- +# FINAL STATUS +# --------------------------------------------------------- +if [ "$OVERALL_SUCCESS" = true ]; then + echo "========================================" + echo -e "${GREEN}All checks passed successfully!${NC}" + echo "========================================" + exit 0 +else + echo "========================================" + echo -e "${RED}Some checks failed.${NC}" + echo "========================================" + exit 1 +fi \ No newline at end of file diff --git a/tests/parameter_checker/runner.sh b/tests/parameter_checker/runner.sh new file mode 100644 index 0000000..e10c3fa --- /dev/null +++ b/tests/parameter_checker/runner.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status. +set -e + +echo "========================================" +echo "Starting Compilation Matrix" +echo "========================================" + +# ------------------------------------------------------------------ +# 1. POSITIVE TESTS (Should Compile) +# ------------------------------------------------------------------ + +# C++ Standards +CPP_STANDARDS=("gnu++11" "gnu++14" "gnu++17" "gnu++2a") + +for std in "${CPP_STANDARDS[@]}"; do + echo "[PASS-CHECK] C++ Source with -std=$std" + make clean --no-print-directory + make TEST_SRC=src_cpp USER_CXXFLAGS="-std=$std" --no-print-directory + echo " -> SUCCESS" +done + +# C Standards +C_STANDARDS=("gnu99" "gnu11" "gnu17") + +for std in "${C_STANDARDS[@]}"; do + echo "[PASS-CHECK] C Source with -std=$std" + make clean --no-print-directory + make TEST_SRC=src_c USER_CFLAGS="-std=$std" --no-print-directory + echo " -> SUCCESS" +done + +# ------------------------------------------------------------------ +# 2. NEGATIVE TESTS (Should FAIL to Compile) +# ------------------------------------------------------------------ +echo "========================================" +echo "Starting Negative Tests (Expect Errors)" +echo "========================================" + +FAIL_CASES=( + "src_fail/fail_color" + "src_fail/fail_duration" + "src_fail/fail_callback" + "src_fail/fail_bool" +) + +# Temporarily turn off 'set -e' so we can capture the failure +set +e + +for test_dir in "${FAIL_CASES[@]}"; do + echo "[FAIL-CHECK] Testing $test_dir (Should fail build)" + + make clean --no-print-directory > /dev/null 2>&1 + + # Run Make and capture output to prevent cluttering logs, + # unless you want to see the specific error. + OUTPUT=$(make TEST_SRC=$test_dir --no-print-directory 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -eq 0 ]; then + echo " -> ERROR: Build succeeded but SHOULD HAVE FAILED!" + echo " (Type checks failed to catch the invalid parameter)" + exit 1 + else + echo " -> SUCCESS: Build failed as expected." + # Optional: Check if the output contains the specific warning message? + # echo "$OUTPUT" | grep "NotificationModule_SetDefaultValue expects" + fi +done + +# Re-enable exit on error +set -e + +echo "========================================" +echo "All tests passed successfully!" +echo "========================================" \ No newline at end of file diff --git a/tests/parameter_checker/src_c/main.c b/tests/parameter_checker/src_c/main.c new file mode 100644 index 0000000..e405743 --- /dev/null +++ b/tests/parameter_checker/src_c/main.c @@ -0,0 +1,54 @@ +#include +#include + +// Dummy callback for C +void my_c_callback(NotificationModuleHandle h, void* ctx) { + (void)h; + (void)ctx; +} + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + NotificationModule_InitLibrary(); + + // Setup variables + NMColor color = {255, 0, 0, 255}; + float duration = 3.0f; + int ctx_data = 100; + + // Test 1: Background Color + // C11+ uses _Generic to check types here. + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_BACKGROUND_COLOR, + color + ); + + // Test 2: Duration + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, + duration + ); + + // Test 3: Callback + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION, + my_c_callback + ); + + // Test 4: Context + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION_CONTEXT, + (void*) &ctx_data + ); + + NotificationModule_AddInfoNotification("C Compatibility Test"); + + NotificationModule_DeInitLibrary(); + return 0; +} \ No newline at end of file diff --git a/tests/parameter_checker/src_cpp/main.cpp b/tests/parameter_checker/src_cpp/main.cpp new file mode 100644 index 0000000..d4d0264 --- /dev/null +++ b/tests/parameter_checker/src_cpp/main.cpp @@ -0,0 +1,68 @@ +#include +#include + +// Dummy callback for testing +void my_callback(NotificationModuleHandle h, void* ctx) { + (void)h; + (void)ctx; +} + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + // 1. Initialize the library + NotificationModule_InitLibrary(); + + // 2. Test Type Checks (Valid Cases) + // These should compile successfully. If the type checks are broken, + // -Werror will cause the build to fail here. + + NMColor color = {255, 255, 255, 255}; + float duration = 5.0f; + bool keep = true; + int ctx_data = 1; + + // Background Color + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_BACKGROUND_COLOR, + color + ); + + // Duration + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, + duration + ); + + // Callback + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION, + my_callback + ); + + // Context + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION_CONTEXT, + (void*)&ctx_data + ); + + // Keep + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_KEEP_UNTIL_SHOWN, + keep + ); + + // 3. Test API usage + NotificationModule_AddInfoNotification("CI Test: Build Successful!"); + + // Deinit + NotificationModule_DeInitLibrary(); + + return 0; +} \ No newline at end of file diff --git a/tests/parameter_checker/src_fail_c/main.c b/tests/parameter_checker/src_fail_c/main.c new file mode 100644 index 0000000..f597a3f --- /dev/null +++ b/tests/parameter_checker/src_fail_c/main.c @@ -0,0 +1,80 @@ +#include +#include + +// Helper macros +#ifdef MAKE_VALID + #define ARG(valid, invalid) (valid) +#else + #define ARG(valid, invalid) (invalid) +#endif + +void my_cb(NotificationModuleHandle h, void* c) { (void)h; (void)c; } +void my_broken_cb(NotificationModuleHandle h, void* c, int) { (void)h; (void)c; } + +int main(int argc, char **argv) { + NotificationModule_InitLibrary(); + + // --------------------------------------------------------- + // TEST CASE: FAIL_COLOR + // --------------------------------------------------------- + #ifdef TEST_FAIL_COLOR + NMColor valid_col = {255, 0, 0, 255}; + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_BACKGROUND_COLOR, + ARG(valid_col, 0xFFFFFFFF) + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_DURATION + // --------------------------------------------------------- + #ifdef TEST_FAIL_DURATION + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, + ARG(5.0f, 5) + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_CALLBACK + // --------------------------------------------------------- + #ifdef TEST_FAIL_CALLBACK + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION, + ARG(my_cb, my_broken_cb) + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_CONTEXT + // --------------------------------------------------------- + #ifdef TEST_FAIL_CONTEXT + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION_CONTEXT, + ARG(NULL, 1.5f) + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_BOOL + // --------------------------------------------------------- + #ifdef TEST_FAIL_BOOL + #ifdef MAKE_VALID + #else + int dummy_int = 0; + #endif + // For C, 1 is a valid boolean (int), so we use a pointer as invalid + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_KEEP_UNTIL_SHOWN, + ARG(1, &dummy_int) + ); + #endif + + NotificationModule_DeInitLibrary(); + return 0; +} \ No newline at end of file diff --git a/tests/parameter_checker/src_fail_cpp/main.cpp b/tests/parameter_checker/src_fail_cpp/main.cpp new file mode 100644 index 0000000..84ed8cb --- /dev/null +++ b/tests/parameter_checker/src_fail_cpp/main.cpp @@ -0,0 +1,80 @@ +#include + +// Helper macros to switch between Valid and Invalid data +#ifdef MAKE_VALID + #define ARG(valid, invalid) (valid) +#else + #define ARG(valid, invalid) (invalid) +#endif + +// Dummy callback +void my_cb(NotificationModuleHandle h, void* c) { (void)h; (void)c; } +void my_broken_cb(NotificationModuleHandle h, void* c, int) { (void)h; (void)c; } + +int main(int argc, char **argv) { + NotificationModule_InitLibrary(); + + // --------------------------------------------------------- + // TEST CASE: FAIL_COLOR (Background Color) + // --------------------------------------------------------- + #ifdef TEST_FAIL_COLOR + NMColor valid_col = {255, 0, 0, 255}; + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_BACKGROUND_COLOR, + ARG(valid_col, 0xFFFFFFFF) // Invalid: int + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_DURATION (Float) + // --------------------------------------------------------- + #ifdef TEST_FAIL_DURATION + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, + ARG(5.0f, 5) // Invalid: int literal + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_CALLBACK (Function Pointer) + // --------------------------------------------------------- + #ifdef TEST_FAIL_CALLBACK + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION, + ARG(my_cb, my_broken_cb) // Invalid: nullptr + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_CONTEXT (Void Pointer) + // --------------------------------------------------------- + #ifdef TEST_FAIL_CONTEXT + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_FINISH_FUNCTION_CONTEXT, + ARG(nullptr, 1.5f) // Invalid: float (nullptr is valid for void*) + ); + #endif + + // --------------------------------------------------------- + // TEST CASE: FAIL_BOOL (Boolean) + // --------------------------------------------------------- + #ifdef TEST_FAIL_BOOL + #ifdef MAKE_VALID + bool valid_bool = true; + #else + int dummy_int = 0; + #endif + NotificationModule_SetDefaultValue( + NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, + NOTIFICATION_MODULE_DEFAULT_OPTION_KEEP_UNTIL_SHOWN, + ARG(valid_bool, &dummy_int) // Invalid: pointer + ); + #endif + + NotificationModule_DeInitLibrary(); + return 0; +} \ No newline at end of file