Properly implement NotificationModule_SetDefaultValue typechecks for C and C++

This commit is contained in:
Maschell 2026-01-15 19:47:44 +01:00
parent 8caeeaa8a8
commit 3792c97d68
14 changed files with 858 additions and 19 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Enforce Unix line endings for shell scripts
*.sh text eol=lf

View File

@ -20,6 +20,8 @@ jobs:
docker build . -t tmp docker build . -t tmp
docker build . -f Dockerfile.buildlocal -t builder docker build . -f Dockerfile.buildlocal -t builder
docker run --rm -v ${PWD}:/project builder make 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 - uses: actions/upload-artifact@master
with: with:
name: lib name: lib

4
.gitignore vendored
View File

@ -7,3 +7,7 @@ CMakeLists.txt
.idea/ .idea/
cmake-build-debug/ cmake-build-debug/
share/libfunctionpatcher.ld share/libfunctionpatcher.ld
*.elf
*.rpx
*.wuhb
tests/parameter_checker/build/

View File

@ -1,4 +1,4 @@
FROM ghcr.io/wiiu-env/devkitppc:20240423 FROM ghcr.io/wiiu-env/devkitppc:20250608
WORKDIR tmp_build WORKDIR tmp_build
COPY . . COPY . .

View File

@ -1,3 +1,3 @@
FROM ghcr.io/wiiu-env/devkitppc:20240423 FROM ghcr.io/wiiu-env/devkitppc:20250608
WORKDIR project WORKDIR project

View File

@ -374,23 +374,20 @@ NotificationModuleStatus NotificationModule_FinishDynamicNotificationWithShake(N
float durationBeforeFadeOutInSeconds, float durationBeforeFadeOutInSeconds,
float shakeDuration); 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"
#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. */
#define NotificationModule_SetDefaultValue(type, valueType, param) NotificationModule_SetDefaultValue(type, valueType, param)
#endif /* __STDC__ >= 1 */
#endif /* gcc >= 4.3 && !__cplusplus */
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #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. */
#define NotificationModule_SetDefaultValue(type, valueType, param) NotificationModule_SetDefaultValue(type, valueType, param)
#endif /* __STDC__ >= 1 */
#endif /* gcc >= 4.3 */

View File

@ -0,0 +1,172 @@
#ifndef NOTIFICATIONS_TYPECHECK_GCC_H
#define NOTIFICATIONS_TYPECHECK_GCC_H
#ifdef __cplusplus
#include <cstddef> /* For std::nullptr_t */
#else
#include <stddef.h> /* 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<typename T>
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<typename T>
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<typename T>
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<typename T>
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<typename T>
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 */

View File

@ -0,0 +1,184 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/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
#-------------------------------------------------------------------------------

View File

@ -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

View File

@ -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 "========================================"

View File

@ -0,0 +1,54 @@
#include <coreinit/thread.h>
#include <notifications/notifications.h>
// 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;
}

View File

@ -0,0 +1,68 @@
#include <coreinit/thread.h>
#include <notifications/notifications.h>
// 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;
}

View File

@ -0,0 +1,80 @@
#include <notifications/notifications.h>
#include <stddef.h>
// 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;
}

View File

@ -0,0 +1,80 @@
#include <notifications/notifications.h>
// 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;
}