diff --git a/src/include/xcelog.h b/src/include/xcelog.h new file mode 100644 index 00000000..aa495e16 --- /dev/null +++ b/src/include/xcelog.h @@ -0,0 +1,93 @@ +#ifndef _XCELOG_H_ +#define _XCELOG_H_ + +#include "tlog.h" +#include + + +static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; +static int xce_log_initialized = 0; + +typedef enum { + XCE_LOG_DEBUG3 = TLOG_DEBUG3, + XCE_LOG_DEBUG2 = TLOG_DEBUG2, + XCE_LOG_DEBUG = TLOG_DEBUG, + XCE_LOG_INFO = TLOG_INFO, + XCE_LOG_NOTICE = TLOG_NOTICE, + XCE_LOG_WARN = TLOG_WARN, + XCE_LOG_ERROR = TLOG_ERROR, + XCE_LOG_FATAL = TLOG_FATAL, + XCE_LOG_OFF = TLOG_OFF, + XCE_LOG_END = TLOG_END +} LogLevel; + +static inline int xce_init_xcecd_log(const char* log_file, + int log_size, + int log_level) +{ + int ret; + pthread_mutex_lock(&log_mutex); + if (!xce_log_initialized) { + ret = tlog_init(log_file, log_size, 6, 0, 0); + if (ret != 0) { + tlog(TLOG_ERROR, "XCECD start tlog failed.\n"); + return -1; + } + + tlog_setlevel(log_level); + xce_log_initialized = 1; + } + pthread_mutex_unlock(&log_mutex); + return 0; +} + +static inline int xce_init_xcecdwk_log(const char* log_file, + int log_size, + int log_level) +{ + int ret; + pthread_mutex_lock(&log_mutex); + if (!xce_log_initialized) { + ret = tlog_init(log_file, log_size, 6, 0, 0); + if (ret != 0) { + tlog(TLOG_ERROR, "XCECD_WORKER start tlog failed.\n"); + return -1; + } + + tlog_setlevel(log_level); + xce_log_initialized = 1; + } + pthread_mutex_unlock(&log_mutex); + return 0; +} + +#define XCECD_LOG(level, format, ...) \ + do { \ + pthread_mutex_lock(&log_mutex); \ + tlog((int)(level), \ + format \ + __VA_OPT__(,) __VA_ARGS__); \ + pthread_mutex_unlock(&log_mutex); \ + } while(0) + +#define XCECD_WORKER_LOG(level, format, ...) \ + do { \ + pthread_mutex_lock(&log_mutex); \ + tlog((int)(level), \ + format \ + __VA_OPT__(,) __VA_ARGS__); \ + pthread_mutex_unlock(&log_mutex); \ + } while(0) + + +/* For different XCE modules we can have customized macros. Here is an example + * of XCECD log micro. + */ +#define CONTROLLER_LOG(level, format, ...) tlog((int)level, format __VA_OPT__(,)__VA_ARGS__) +#define SCHEDULER_LOG(level, format, ...) tlog((int)level, format __VA_OPT__(,)__VA_ARGS__) + +static inline void xce_log_fini() { + tlog_exit(); +} + +#endif diff --git a/src/meson.build b/src/meson.build index 2396118b..182e8c45 100644 --- a/src/meson.build +++ b/src/meson.build @@ -33,7 +33,16 @@ endif include_dir = include_directories('include', 'include/xce') +libcjson_dep = dependency('libcjson') +libcyaml_dep = dependency('libcyaml') +libyaml_dep = dependency('yaml-0.1') + +yaml_deps = [ + libcyaml_dep, + libyaml_dep +] + subdir('lib') subdir('controller') -# subdir('worker') +subdir('worker') # subdir('scheduler') diff --git a/src/nixSupport/xce.nix b/src/nixSupport/xce.nix index 408b7888..dbb36440 100644 --- a/src/nixSupport/xce.nix +++ b/src/nixSupport/xce.nix @@ -28,6 +28,7 @@ stdenv.mkDerivation { libtirpc readline ncurses + cjson capnpc which findutils @@ -38,6 +39,8 @@ stdenv.mkDerivation { linux-pam tinylog unity_test_with_color + libcyaml + libyaml ]; build-system = [ pkgs.meson ]; diff --git a/src/worker/README.md b/src/worker/README.md new file mode 100644 index 00000000..aa55560f --- /dev/null +++ b/src/worker/README.md @@ -0,0 +1,101 @@ +# Cross-Platform Xcecd Process + +This is a cross-platform process manager implementation in C that supports both Linux and Windows platforms. The xcecd provides the following features: + +- Sending status to the remote controller module at port 6789 and receive job requests +- Maintains a configurable thread pool for request processing +- Periodically collects and updates system resource status +- Manages child processes spawned from received commands +- Caches job status information +- Uses Unix Domain Sockets for communication between the main process and worker processes + +## Building + +### Prerequisites + +- C compiler with C11 support +- Windows: Visual Studio +- Linux: GCC and development tools + +### Build Instructions + +On Linux +```bash +make +``` + +On Windows +```bash +nmake +``` + +## Configuration + +The xcecd can be configured through the following parameters in `main.c`: + +- `thread_pool_size`: Number of threads in the thread pool (default: 4) +- `resource_update_interval`: Interval for updating system resource status in seconds (default: 1) +- `job_status_update_interval`: Interval for updating job status in seconds (default: 1) +- `port`: Port number to listen on (default: 7869) +- `acct_file_path`: Path to the accounting file (default: `/tmp/xcecd.acct`) +- `log_file_path`: Path to the log file (default: `/tmp/xcecd.log`) +- `domain_sock_path`: Path to the Unix Domain Socket file (default: `/tmp/xcecd.sock`) +- `remote_host`: Remote server address (default: `lchen02`) +- `remote_port`: Remote server port (default: 6789) +- `update_interval`: Interval for sending status updates to the remote server (default: 1 second) + +## Usage + +### Starting the Xcecd + +On Windows: +```bash +xcecd.exe +``` + +On Linux: +```bash +./xcecd +``` + +## Features + +1. **Cross-Platform Support** + - Works on both Windows and Linux + - Abstracts platform-specific implementations + +2. **Thread Pool** + - Configurable number of worker threads + - Efficient request handling + +3. **System Monitoring** + - CPU usage monitoring + - Memory usage monitoring + - Disk space monitoring + +4. **Process Management** + - Spawns child processes for received commands + - Tracks process status + - Manages process lifecycle + +5. **Network Communication** + - TCP socket-based communication + - Simple text-based protocol + +6. **Unix Domain Socket Communication** + - Uses Unix Domain Sockets for communication between the main process and worker processes + +## Architecture + +The xcecd is composed of several key components: + +- **Main Process**: Coordinates all components and manages the lifecycle +- **Thread Pool**: Handles concurrent request processing +- **System Monitor**: Collects and updates system resource information +- **Process Manager**: Manages child processes and their status +- **Network Handler**: Handles network communication and request receiving +- **Resource Sync**: Synchronizes and aggregates resource collection thread, system monitor, and job information + +## Contributing + +Feel free to submit issues and enhancement requests! diff --git a/src/worker/conf/worker.yaml b/src/worker/conf/worker.yaml new file mode 100644 index 00000000..b3ccfde3 --- /dev/null +++ b/src/worker/conf/worker.yaml @@ -0,0 +1,6 @@ +--- +workerconf: + management_list: ["hostA", "hostB"] + controller_port: 9990 + log_level: "info" + log_dir: "/tmp" diff --git a/src/worker/include/connection_handler.h b/src/worker/include/connection_handler.h new file mode 100644 index 00000000..2a035f73 --- /dev/null +++ b/src/worker/include/connection_handler.h @@ -0,0 +1,29 @@ +/* 此模块为预留功能,它提供了xcecd处理远程连接的可能。xcecd默认会打开一个监听端口并能接受外部请求。 + * 此保留功能是为了提供LSF兼容性,默认不使能。 + */ +#ifndef CONNECTION_HANDLER_H +#define CONNECTION_HANDLER_H + +#include + +typedef struct network_handler network_handler_t; + +// 网络请求回调函数类型 +typedef const char* (*request_handler_t)(const char* request, void* user_data); + +// 创建网络处理器 +network_handler_t* network_handler_create(uint16_t port); + +// 设置请求处理回调 +void network_handler_set_callback(network_handler_t* handler, request_handler_t callback, void* user_data); + +// 启动网络监听 +int network_handler_start(network_handler_t* handler); + +// 停止网络监听 +void network_handler_stop(network_handler_t* handler); + +// 销毁网络处理器 +void network_handler_destroy(network_handler_t* handler); + +#endif // CONNECTION_HANDLER_H diff --git a/src/worker/include/controller_connector.h b/src/worker/include/controller_connector.h new file mode 100644 index 00000000..dbfad160 --- /dev/null +++ b/src/worker/include/controller_connector.h @@ -0,0 +1,62 @@ +#ifndef CONTROL_CONNECTOR_H +#define CONTROL_CONNECTOR_H +#include "thread_pool.h" +#include "xcecd_utils.h" +#include "client.h" +#include "xcectx.h" +#include "controller.capnp.h" + +typedef struct { + xce_client_config_t* conf; + endpoint_t ep; + xce_ctx_t ctx; +} api_ctx_t; + +typedef struct controller_connector_ctx_s { + void *thread_pool; + void *process_manager; + char *remote_host; + char *virtual_name; + void **worker_list; + int remote_port; + int join_interval; + bool running; +} controller_connector_ctx_t; +extern controller_connector_ctx_t *cc_ctx; + +typedef struct { + int (*job_finished)(xcecd_job_t *job); + int (*job_stopped)(uint64_t jobid); + int (*job_resumed)(uint64_t jobid); + int (*job_start_error)(uint64_t jobid); + int (*job_status_error)(uint64_t jobid); + int (*job_ckpted)(uint64_t jobid, int status, const char *info); + int (*job_remote_start)(uint64_t jobid, const char *hostname, const char *command); + bool (*reply_startjob)(xcecd_job_t *job); +} cc_callbacks_t; + +extern void xcecd_process_command(void* arg); + +int start_controller_connector_thread(task_func_t function, void* arg); +int xcecd_join_cluster(hostattrs_t* attrs); +bool controller_connector_start(void); +bool xcecd_reply_startjob(xcecd_job_t *job); +int xcecd_job_finished(xcecd_job_t *job); +int xcecd_job_stopped(uint64_t jobid); +int xcecd_job_resumed(uint64_t jobid); +int checking_connection(); +int xcecd_job_start_error(uint64_t jobid); +int xcecd_job_status_error(uint64_t jobid); +int xcecd_job_ckpted(uint64_t jobid, int status, const char *info); +int xcecd_job_remote_start(uint64_t jobid, + const char *hostname, + const char *command); +const cc_callbacks_t* get_cc_callbacks(void); +int xcecd_task_fail(uint64_t jobid, int clientidx, + int taskidx, int code); +int xcecd_jobpost_done(uint64_t jobid); +int xcecd_notify_job_fail(uint64_t jobid, + int stage, + int code); + +#endif // CONTROL_CONNECTOR_H diff --git a/src/worker/include/hardware_monitor.h b/src/worker/include/hardware_monitor.h new file mode 100644 index 00000000..bb87fc7a --- /dev/null +++ b/src/worker/include/hardware_monitor.h @@ -0,0 +1,28 @@ +#ifndef HARDWARE_MONITOR_H +#define HARDWARE_MONITOR_H + +#include +#include +#include + +typedef struct hardware_monitor hardware_monitor_t; + +// 创建硬件监控器 +hardware_monitor_t* hardware_monitor_create(const char* acct_file); + +// 销毁硬件监控器 +void hardware_monitor_destroy(hardware_monitor_t* monitor); + +// 更新硬件信息 +bool hardware_monitor_update(hardware_monitor_t* monitor); + +// 获取CPU核心数 +int hardware_monitor_get_cpu_cores(hardware_monitor_t* monitor); + +double hardware_monitor_get_total_memory(hardware_monitor_t* monitor); + +double hardware_monitor_get_free_memory(hardware_monitor_t* monitor); + +bool hardware_monitor_get_memory_info(int64_t* total_memory, int64_t* free_memory); + +#endif // HARDWARE_MONITOR_H diff --git a/src/worker/include/inter_job_utils.h b/src/worker/include/inter_job_utils.h new file mode 100644 index 00000000..8172c13f --- /dev/null +++ b/src/worker/include/inter_job_utils.h @@ -0,0 +1,23 @@ +#ifndef _INTER_JOB_UTILS_H +#define _INTER_JOB_UTILS_H + +#define CMD_MAX_LENGTH 4096 +#define URL_MAX_LENGTH 256 +#define TTY_END_PORT 10000 +#define XJOB_END_PORT 5999 + +typedef struct { + int tty_port; + int xjob_port; +} port_info_t; + + +int check_port_availability(int port_number); +int find_available_port(int start_port, int end_port); +int check_port(port_info_t port, int interactive_mode); +char* generate_cmd(char* job_cmd, port_info_t port, int interactive_mode); +char* generate_url(port_info_t port, int interactive_mode); +int export_display_env(int port); + + +#endif /* _INTER_JOB_UTILS_H */ diff --git a/src/worker/include/job_information.h b/src/worker/include/job_information.h new file mode 100644 index 00000000..8e3db3fb --- /dev/null +++ b/src/worker/include/job_information.h @@ -0,0 +1,211 @@ +#ifndef JOB_INFORMATION_H +#define JOB_INFORMATION_H + +#include "xcecd_utils.h" + +#include +#include + +#define NBUILTINDEX 11 +#define MAX_LSB_NAME_LEN 60 +#define MAXFILENAMELEN 256 + + +struct job_specs { + char *job_name; + char *job_owner; + char *job_group; + char *job_project; + char *job_class; + char *job_type; + char *job_queue; + char *job_start_time; + char *job_end_time; + char *job_window; + char *job_flags; + char *job_exec_host; + char *job_exec_port; + char *job_exec_path; + char *job_exec_args; + char *job_exec_env; + char *job_exec_dir; + char *job_exec_user; + char *job_exec_group; + char *job_exec_shell; + char *job_exec_type; + char *job_exec_flags; + char *job_exec_window; + char *job_exec_start_time; + char *job_exec_end_time; + char *job_exec_queue; + char *job_exec_project; + char *job_exec_class; +}; + +struct xce_rusage { + float ru_utime; + float ru_stime; + float ru_maxrss; + float ru_ixrss; + float ru_idrss; + float ru_isrss; + float ru_minflt; + float ru_majflt; + float ru_nswap; + float ru_inblock; + float ru_oublock; + float ru_msgsnd; + float ru_msgrcv; + float ru_nsignals; + float ru_nvcsw; + float ru_nivcsw; +}; + +struct job_rusage { + float cpu_time; + float max_rss; + float ixrss; + float idrss; + float isrss; + float minflt; + float majflt; + float nswap; + float inblock; + float oublock; + float msgsnd; + float msgrcv; + float nsignals; + float nvcsw; + float nivcsw; +}; + +struct res_val { + char *selectStr; + int nphase; + int order[NBUILTINDEX]; + int genClass; + float *val; + int nindex; + int *indicies; + int *rusgBitMaps; + int duration; + float decay; + int numHosts; + int maxNumHosts; + int pTile; + int options; + int selectStrSize; + char **xorExprs; +}; + +// 以上结构体定义为了方便前期调试,后续可能会根据实际情况进行修改,或者从新定义在控制模块中. +// 它们包含 job_specs、xce_rusage、job_rusage、res_val 四个结构体,分别用于作业规格、资源使用情况、作业资源使用情况、资源值的定义 + +struct client_node { + struct client_node *prev; + struct client_node *next; + int chanfd; + struct sockaddr_in from; + int job_type; + long job_id; // 作业ID,暂定为长整型,后续可能会修改 + struct job_card *jp; +}; + +struct job_card { + struct job_card *next; + struct job_card *prev; + gid_t exec_gid; // 执行用户组ID + char exec_user_name[MAX_LSB_NAME_LEN]; // 执行用户名 + int not_reported; + time_t wind_edge; // 时间窗口边界 + windows_t *week[8]; // 一周的时间窗口 + + char active; + char time_expire; + char missing; + char controller_restarted; + time_t wind_warn_time; + int runtime; + int w_status; + + /* pre-exec report flag */ + float cpu_time; + time_t last_chkpnt_time; + int mig_count; + struct job_specs job_specs; + struct xce_rusage xce_rusage; + int need_report_rusage; + int cleanup_pid; + int collected_child; + int exec_job_flag; +#define JOB_EXEC_QPRE_OK 0x1 +#define JOB_EXEC_QPRE_KNOWN 0x2 +#define JOB_EXEC_STARTED 0x4 + + char *stdin_file; // 标准输入文件 + time_t last_controller_time; // 最后一次和控制器连接上的时间 + + struct job_rusage run_rusage; // 运行时资源使用情况 + struct job_rusage alloc_rusage; // 控制器资源分配情况 + struct job_rusage max_rusage; // 最大资源使用情况 + + int delievered_message_id; + struct client_node *client; + int reg_op_flag; + struct job_rusage work_rusage; + int job_sup_status; +#define REG_CHKPNT 0x1 +#define REG_SIGNAL 0x2 +#define REG_RUSAGE 0x4 +#define REG_NICE 0x8 + struct res_val *resume_cond_val; + struct res_val *stop_cond_val; + int act_flags; + int act_status; + int act_reasons; + int act_sub_reasons; + int exit_pid; + int job_done; + time_t last_check; + char *act_cmd; + char *exit_file; + char *cluster_name; + int server_socket; + int cross_platforms; + char *spooled_exec; + char post_job_started; + char user_job_succeed; +}; + +struct job_setup { + long job_id; + int job_status; + float cpu_time; + int w_status; + struct xce_rusage xce_rusage; + int reason; + int job_pid; + int job_pgid; + int exec_gid; + int exec_uid; + char exec_user_name[MAX_LSB_NAME_LEN]; + char exec_home[MAXFILENAMELEN]; + char exec_cwd[MAXFILENAMELEN]; + int exec_job_flag; + +#define LSB_PRE_ABORT 99 +}; + + +void init_job_setup(struct job_setup* setup, const char* data); + +void init_job_card(struct job_card* card, const char* data); + +int update_job_card(struct job_card* card, const char* data); + +void cleanup_job_card(struct job_card* card); + +struct client_node* init_client_node(long id); + + +#endif // JOB_INFORMATION_H \ No newline at end of file diff --git a/src/worker/include/node_information.h b/src/worker/include/node_information.h new file mode 100644 index 00000000..6e1c6458 --- /dev/null +++ b/src/worker/include/node_information.h @@ -0,0 +1,117 @@ +#ifndef NODE_INFORMATION_H_ +#define NODE_INFORMATION_H_ + +#include +#include + +#include "xcecd_utils.h" +#include "connection_handler.h" +#include "process_manager.h" +#include "system_monitor.h" +#include "hardware_monitor.h" +#include "thread_pool.h" + + +#define MAXXCENAMELEN 40 +#define MAXNODENAMELEN 256 +#define MAXCANDHOSTS 10 + +#define DEFAULT_AFTER_HOUR "19:00-7:00 5:19:00-1:7:00" + +struct time_window { + char *window_name; // 窗口名称 + windows_t *week[8]; + time_t window_edge; +}; + +struct node_status { + short node_id; // 节点ID + int max_cpu_cores; // CPU核心数 + int max_memory; // 内存大小 + int num_disk; // 磁盘数量 + int max_swap; // 交换空间大小 + int max_network; // 网络带宽 + unsigned short port_number; // 端口号 + char node_type[MAXXCENAMELEN]; // 节点类型 + char node_arch[MAXXCENAMELEN]; // 节点架构 +}; + +typedef struct host_node host_node_t; +struct host_node { + char *node_name; // 节点名称 + short node_number; // 节点编号 + short node_model_number; // 节点型号编号 + short node_type_number; // 节点类型编号 + in_addr_t *node_ip; // 节点IP地址 + struct node_status node_status; // 节点状态 + char info_valid; // 信息是否有效 + unsigned char protocol_version; // 协议版本 + short available_high; // 高可用性 + short available_low; // 低可用性 + short use; // 使用情况 + char *windows; // 时间窗口 + windows_t *week[8]; // 一周的时间窗口 + time_t window_edge; // 时间窗口边界 + time_t last_jack_time; // 最后一次插入时间 + int node_inactivity_count; // 节点不活动计数 + int *status; // 状态 + float *busy_threshold; // 繁忙阈值 + float *load_index; // 负载指数 + float *user_load_index; // 用户负载指数 + int info_mask; // 信息掩码 + int load_mask; // 负载掩码 + int *resource_bitmap; // 资源位图 + int *dynamic_resource_bitmap; // 动态资源位图 + int number_of_instances; // 实例数量 + host_node_t *next; // 下一个节点 + time_t expire_time; // 过期时间 + uint8_t migrant; +}; + +struct cluster_node { + short cluster_number; // 集群编号 + char *cluster_name; // 集群名称 + int status; // 状态 + in_addr_t candidate_address_list[MAXCANDHOSTS]; // 候选地址列表 + char *primary_node_name; // 主节点名称 + unsigned short primary_address; // 主地址 + unsigned short primary_port; // 主端口 + char primary_known; // 主节点可识别 + int primary_inactivity_count; // 主节点不活动计数 + host_node_t *primary_pointer; // 主节点指针 + host_node_t *previous_primary_pointer; // 上一个主节点指针 + unsigned short checksum; // 校验和 + int number_of_nodes; // 节点数量 + int number_of_clients; // 客户端数量 + host_node_t *node_list; // 节点列表 + host_node_t *client_list; // 客户端列表 + int manager_id; // 管理器ID + char *manager_name; // 管理器名称 + int channel_fd; // 通道文件描述符 + int number_of_index; // 索引数量 + int number_of_user_index; // 用户索引数量 + int user_index_class; // 用户索引类 + char **load_index_names; // 负载指数名称 + int number_of_admins; // 管理员数量 + int *admin_ids; // 管理员ID + char **admin_names; // 管理员名称 + int number_of_resouces; // 资源数量 + int *resource_bitmaps; // 资源位图 + int *hosttype_bitmaps; // 主机类型位图 + int *hostmodel_bitmaps; // 主机模型位图 + int number_of_shared_resources; // 共享资源数量 + char **shared_resources; // 共享资源 + struct cluster_node *next; +}; + +// client_node 和 minSLimConfData 数据结构需要添加吗? + +host_node_t* host_node_create(void); + +int host_node_update(host_node_t* node_ptr, host_node_t* new_node); + +void free_host_node(host_node_t* node_ptr); + +int get_cpu_usage(int total_cores, int used_cores); + +#endif // NODE_INFORMATION_H_ diff --git a/src/worker/include/process_manager.h b/src/worker/include/process_manager.h new file mode 100644 index 00000000..fadeac6e --- /dev/null +++ b/src/worker/include/process_manager.h @@ -0,0 +1,128 @@ +#ifndef PROCESS_MANAGER_H +#define PROCESS_MANAGER_H + +#include +#include + +#ifdef _WIN32 +#include +typedef HANDLE process_handle_t; +#else +#include +#include +typedef pid_t process_handle_t; +#endif +#include "sqlite3.h" + + +typedef struct { + process_handle_t handle; + char* command; // command line + char* work_dir; // working directory + int status; // process status (0: stopped, 1: running) + int exit_code; // exit code + int output_fd; // output file descriptor + char* log_file; // log file path +#ifdef _WIN32 + HANDLE pipe_read; // pipe read handle + HANDLE monitor_thread; // monitor thread handle + bool monitor_active; // monitor thread active flag +#endif +} process_info_t; + +typedef struct process_manager { + process_info_t* processes; + size_t process_count; + int update_interval; + bool running; + bool initialized; +#ifdef _WIN32 + HANDLE thread; + CRITICAL_SECTION lock; +#else + pthread_t thread; + pthread_mutex_t lock; +#endif +} process_manager_t; + +/** + * @brief initialize process info structure + * @param proc process info structure + * @return void + */ +void process_info_init(process_info_t* proc); + +/** + * @brief initialize process info structure + * @return process_manager_t* if success, NULL if failed + */ +process_manager_t* process_manager_create(void); + +/** + * @brief start a new process + * @param manager process manager without resource limits + * @param command command with arguments + * @return 0 if success, -1 if failed + */ +int process_manager_start_process(process_manager_t* manager, const char* command); + +/** + * @brief get the number of processes + * @param manager process manager + * @return the number of processes + */ +int get_process_count(process_manager_t* manager); + +/** + * @brief get the current process pid + * @param manager process manager + * @return the current process pid + */ +int get_current_process_pid(process_manager_t* manager); + +/** + * @brief update the process status in the list + * @param manager process manager + * @return true if success, false if failed + */ +bool process_manager_update(process_manager_t* manager); + +/** + * @brief get process info by handle + * @param manager process manager + * @param handle process handle + * @return process_info_t* if success, NULL if failed + */ +process_info_t* process_manager_get_process_info(process_manager_t* manager, process_handle_t handle); + +/** + * @brief terminate a process + * @param manager process manager + * @param handle process handle + * @return 0 if success, -1 if failed + */ +int process_manager_terminate_process(process_manager_t* manager, process_handle_t handle); + +/** + * @brief destroy process manager + * @param manager process manager + * @return void + */ +void process_manager_destroy(process_manager_t* manager); + +/** + * @brief remove a process from the list + * @param manager process manager + * @param handle process handle + * @return 0 if success, -1 if failed + */ +int process_manager_remove_process(process_manager_t* manager, process_handle_t handle); + +/** + * @brief free process manager + * @param manager process manager + * @return void + */ +void process_manager_free(process_manager_t* manager); + +#endif // PROCESS_MANAGER_H diff --git a/src/worker/include/process_worker_manager.h b/src/worker/include/process_worker_manager.h new file mode 100644 index 00000000..3fc7ae6c --- /dev/null +++ b/src/worker/include/process_worker_manager.h @@ -0,0 +1,35 @@ +/** + * @file process_worker_manager.h + * @brief This file contains the declaration of the worker process manager functions. + * It uses to manage worker process with resource limits. And it relies on + * process_manager.h to manage the worker process. This module disabled by default. + */ +#ifndef _PROCESS_WORKER_MANAGER_H_ +#define _PROCESS_WORKER_MANAGER_H_ + +#include "process_manager.h" +#include "xcecd_worker_utils.h" +#include "xcecd_utils.h" +#include "xcelog.h" + +/** + * @brief start worker without resource limits (disabled by default) + * @param command command with arguments + * @return 0 if success, -1 if failed + */ +int process_manager_start_worker(process_manager_t* manager); + +/** + * @brief update the process status in the list + * @param manager process manager + * @param worker worker process + * @param job XCE job information + * @param db database information + * @return 0 if success, -1 if failed + */ +bool worker_process_update(process_manager_t* manager, + xcecd_worker_t* worker, + xcecd_job_t* job, + sqlite3* db); + +#endif // _PROCESS_WORKER_MANAGER_H_ \ No newline at end of file diff --git a/src/worker/include/resource_sync.h b/src/worker/include/resource_sync.h new file mode 100644 index 00000000..b4b7e096 --- /dev/null +++ b/src/worker/include/resource_sync.h @@ -0,0 +1,29 @@ +#ifndef RESOURCE_SYNC_H +#define RESOURCE_SYNC_H + +#include "hardware_monitor.h" +#include "node_information.h" +#include + +// 资源同步模块结构体 +typedef struct resource_sync resource_sync_t; + +// 创建资源同步模块 +resource_sync_t* resource_sync_create(void); + +// 销毁资源同步模块 +void resource_sync_destroy(resource_sync_t* sync); + +// 启动资源同步模块 +bool resource_sync_start(resource_sync_t* sync); + +// 停止资源同步模块 +void resource_sync_stop(resource_sync_t* sync); + +// 更新资源同步模块 +bool resource_sync_update(resource_sync_t* sync); + +// 读取资源同步模块 +void resource_sync_read(resource_sync_t* sync, host_node_t** node); + +#endif // RESOURCE_SYNC_H \ No newline at end of file diff --git a/src/worker/include/static_array.h b/src/worker/include/static_array.h new file mode 100644 index 00000000..7b7b7dbc --- /dev/null +++ b/src/worker/include/static_array.h @@ -0,0 +1,33 @@ +#ifndef _STATIC_ARRAY_H_ +#define _STATIC_ARRAY_H_ + +#include +#include +#include +#include +#include + +#define MAX_ARRAY_SIZE 128 + +typedef struct { + unsigned char data[MAX_ARRAY_SIZE * 64]; + size_t element_size; + size_t count; + pthread_mutex_t lock; +} StaticArray; + + +void init_static_array(StaticArray* arr, size_t element_size); + +bool push_static(StaticArray* arr, void* value); + +bool pop_static(StaticArray* arr, void* out_value); + +bool pop_static_from_bottom(StaticArray* arr, void* out_value); + +void clear_static(StaticArray* arr); + +bool get_element(StaticArray* arr, size_t index, void* out_value); + + +#endif // _STATIC_ARRAY_H_ diff --git a/src/worker/include/system_monitor.h b/src/worker/include/system_monitor.h new file mode 100644 index 00000000..98501eb3 --- /dev/null +++ b/src/worker/include/system_monitor.h @@ -0,0 +1,29 @@ +#ifndef SYSTEM_MONITOR_H +#define SYSTEM_MONITOR_H + +#include +#include + +typedef struct { + double cpu_usage; // CPU使用率(百分比) + uint64_t memory_total; // 总内存(字节) + uint64_t memory_used; // 已使用内存(字节) + uint64_t disk_total; // 总磁盘空间(字节) + uint64_t disk_used; // 已使用磁盘空间(字节) +} system_status_t; + +typedef struct system_monitor system_monitor_t; + +// 创建系统监控器 +system_monitor_t* system_monitor_create(void); + +// 获取当前系统状态 +const system_status_t* system_monitor_get_status(system_monitor_t* monitor); + +// 更新系统状态 +bool system_monitor_update(system_monitor_t* monitor); + +// 销毁系统监控器 +void system_monitor_destroy(system_monitor_t* monitor); + +#endif // SYSTEM_MONITOR_H diff --git a/src/worker/include/thread_pool.h b/src/worker/include/thread_pool.h new file mode 100644 index 00000000..9c3bf01b --- /dev/null +++ b/src/worker/include/thread_pool.h @@ -0,0 +1,100 @@ +#ifndef THREAD_POOL_H +#define THREAD_POOL_H + +#include "xcecd_utils.h" +#include "xcecd_cgroups.h" +#include +#include +#include +#include + +#define IPC_BUFFER_SIZE 256 +#define MAX_MSG_SIZE 4096 +#define MAX_QUEUE_SIZE 1024 + +// 任务函数类型 +typedef void (*task_func_t)(void* arg); + +// 作业执行任务消息队列 +typedef struct { + pid_t target_pid; + uint32_t ntasks; + uint32_t clientidx; + uint64_t xce_jobid; + task_func_t func; + job_startinfo_t* startinfo; + res_limits_t* limits; +#ifndef DBUS_MANAGER + void* manager; +#endif +} fork_task_t; + +//作业控制消息队列 +typedef struct { + uint64_t jobid; + pid_t target_pid; + int signal; + int force; + bool checkpoint; + int ckpt_kill; + char* ckpt_method; + task_func_t func; +} job_control_task_t; + +// 消息队列结构 +typedef struct { + void *messages; + task_func_t* func; + size_t msg_size; + size_t capacity; + size_t head; + size_t tail; + size_t count; + pthread_mutex_t lock; + pthread_cond_t not_empty; + pthread_cond_t not_full; +} message_queue_t; + +typedef struct { + long system_tid; // 系统线程ID + size_t pool_tid; // 线程池中的线程ID (1-N) +} thread_id_map_t; + +typedef struct { + size_t num_threads; + pthread_t* threads; + bool running; + thread_id_map_t* thread_map; + message_queue_t fork_queue; + message_queue_t control_queue; +} thread_pool_t; + +enum { + JOB_CONTROL_STOP, + JOB_CONTROL_PAUSE, + JOB_CONTROL_RESUME, + JOB_CONTROL_RESTART, + JOB_CONTROL_KILL, + JOB_CONTROL_STATUS +}; + + +thread_pool_t* thread_pool_create(size_t num_threads); + +void thread_pool_destroy(thread_pool_t* pool); + +message_queue_t* message_queue_create(size_t capacity, size_t msg_size); + +void message_queue_destroy(message_queue_t* queue); + +int queue_push(message_queue_t* queue, void* message); + +int queue_pop(message_queue_t* queue, void* message); + +int submit_fork_task(thread_pool_t* pool, fork_task_t* task); + +int submit_job_control_task(thread_pool_t* pool, job_control_task_t* task); + +void job_control_signal(void* arg); + +#endif // THREAD_POOL_H diff --git a/src/worker/include/uv_ipc.h b/src/worker/include/uv_ipc.h new file mode 100644 index 00000000..88caaeb3 --- /dev/null +++ b/src/worker/include/uv_ipc.h @@ -0,0 +1,170 @@ +#ifndef UV_IPC_H +#define UV_IPC_H + +#include "xcecd_utils.h" +#include "uv.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define HOSTNAME_LENGTH 256 +#define MESSAGE_LENGTH 256 +#define COMMAND_LENGTH 4096 +#define METHOD_LENGTH 64 +#define PATH_LENGTH 1024 +#define EXDATA_LENGTH 16 * 1024 + +typedef enum { + MSG_GET_JOB, + MSG_JOB_ASSIGNED, + MSG_JOB_STATUS, + MSG_JOB_COMPLETED, + MSG_JOB_SIGNAL, + MSG_JOB_SIGNAL_ACK, + MSG_JOB_CKPT, + MSG_JOB_CKPT_ACK, + MSG_JOB_CKPT_RESULT, + MSG_REMOTE_START_JOB, + MSG_REMOTE_START_JOB_ACK, + MSG_HEARTBEAT, + MSG_ERROR +} message_type_t; + +typedef struct { + int type; +} heartbeat_message_t; + +typedef struct { + uint64_t job_id; + int job_pid; + int status_code; + int worker_pid; + int worker_status; + int exit_code; + int stage; + bool post_done; + char status_message[MESSAGE_LENGTH]; + char start_exdata[MAXLINELEN]; +} job_status_t; + +typedef struct { + uint64_t job_id; + int ntasks; + char cmd[COMMAND_LENGTH]; + char q_pre_exec[COMMAND_LENGTH]; + char q_post_exec[COMMAND_LENGTH]; + char job_pre_exec[COMMAND_LENGTH]; + char job_post_exec[COMMAND_LENGTH]; + char exdata[EXDATA_LENGTH]; +} job_assignment_t; + +typedef struct { + uint64_t job_id; + int signal; + bool force; +} job_signal_t; + +typedef struct { + uint64_t job_id; + int kill; // 0: do not kill process + char method[METHOD_LENGTH]; + char img_path[PATH_LENGTH]; + char parent_path[PATH_LENGTH]; +} job_ckpt_t; + +typedef struct { + uint64_t job_id; + int status; // 0: success, 1: fail + char restore_cmd[COMMAND_LENGTH]; +} job_ckpt_result_t; + +typedef struct { + uint64_t jobid; + char hostname[HOSTNAME_LENGTH]; + char command[COMMAND_LENGTH]; +} remote_start_job_t; + +typedef struct { + int success; // 1: success, 0: failure + char error_message[MESSAGE_LENGTH]; +} remote_start_job_ack_t; + +typedef struct { + message_type_t type; + union { + job_status_t status; + job_assignment_t assignment; + job_signal_t signal; + job_ckpt_t ckpt; + job_ckpt_result_t ckpt_result; + remote_start_job_t remote_start; + remote_start_job_ack_t remote_start_ack; + char error_message[MESSAGE_LENGTH]; + } data; + int worker_pid; +} ipc_message_t; + +//#undef FREEUP +//#define FREEUP(ptr) do { if (ptr) { free(ptr); ptr = NULL; } } while(0) + +extern int child_processes; +extern int active_jobs; +extern int exit_flag; + +extern uv_loop_t* loop; + +typedef void (*job_callback_t)(job_assignment_t* info); +typedef void (*connection_callback_t)(bool success); +typedef void (*worker_reconnect_callback_t)(bool connected); + +typedef void (*job_status_callback_t)(job_status_t* job_status); +typedef void (*client_disconnect_callback_t)(uint64_t client_id); +typedef void (*heartbeat_callback_t)(uv_stream_t* client); +typedef void (*job_signal_callback_t)(uint64_t job_id, int signal, bool force); +typedef void (*job_ckpt_callback_t)(uint64_t job_id, int kill, const char* method); +typedef void (*job_ckpt_result_callback_t)(uint64_t job_id, int status, const char* restore_cmd); + +bool ipc_master_init(const char* socket_path); +void ipc_master_run(uv_run_mode mode); +void ipc_master_stop(); +bool ipc_master_check_connection(); +void ipc_master_set_status_callback(job_status_callback_t callback); +void ipc_master_set_disconnect_callback(client_disconnect_callback_t callback); +void ipc_master_set_heartbeat_callback(heartbeat_callback_t callback); +bool ipc_master_send_job_signal(uint64_t job_id, int signal, bool force); +bool ipc_master_send_job_ckpt(uint64_t job_id, int kill, const char* method); +void ipc_master_set_ckpt_callback(job_ckpt_callback_t callback); +void ipc_master_set_ckpt_result_callback(job_ckpt_result_callback_t callback); + +bool ipc_worker_init(const char* socket_path); +void ipc_worker_connect(connection_callback_t callback); +void ipc_worker_reconnect(worker_reconnect_callback_t callback); +void ipc_worker_run(uv_run_mode mode); +void ipc_worker_stop(); +void ipc_worker_set_job_callback(job_callback_t callback); +void ipc_worker_set_reconnection_callback(worker_reconnect_callback_t callback); +void ipc_worker_request_job(); +void ipc_worker_send_status(job_runtime_info_t* job_info, + const char* status_message); +void ipc_worker_job_completed(uint64_t job_id); +bool ipc_worker_check_connection(); +void ipc_worker_send_signal_ack(uint64_t job_id, int signal, bool force); +void ipc_worker_set_signal_callback(job_signal_callback_t callback); +bool ipc_worker_send_ckpt_ack(uint64_t job_id, int kill, const char* method); +void ipc_worker_set_ckpt_callback(job_ckpt_callback_t callback); +bool ipc_worker_send_ckpt_result(uint64_t job_id, int status, const char* restore_cmd); + +void on_write_complete(uv_write_t* req, + int status); + +uv_loop_t* ipc_get_event_loop(); + +#ifdef __cplusplus +} +#endif + +#endif /* UV_IPC_H */ diff --git a/src/worker/include/uv_proc.h b/src/worker/include/uv_proc.h new file mode 100755 index 00000000..4022d84a --- /dev/null +++ b/src/worker/include/uv_proc.h @@ -0,0 +1,182 @@ +#ifndef UV_PROC_H +#define UV_PROC_H + +#include "uv.h" +#include +#include + +/** + * process status enumeration + */ +typedef enum { + UV_PROC_IDLE, + UV_PROC_RUNNING, + UV_PROC_COMPLETED, + UV_PROC_ERROR, + UV_PROC_TERMINATED, + UV_PROC_UNKNOWN +} uv_proc_status_t; + +/** + * process exit callback function type + * @param pid process ID + * @param exit_status exit status code + * @param term_signal termination signal (if terminated by signal) + * @param user_data user data passed to the callback + */ +typedef void (*uv_proc_exit_cb)(int pid, int exit_status, + int term_signal, void* user_data); + +/** + * process manager structure + */ +typedef struct uv_proc_manager_s { + uint64_t job_id; // job ID + uv_loop_t* loop; // event loop shared with ipc + uv_process_t* active_proc; // current active process + uv_proc_status_t status; // current process status + int last_exit_code; // last exit code + int last_term_signal; // last termination signal + void* user_data; // user data for callback + uv_proc_exit_cb exit_cb; // exit callback function + + uv_process_t** proc_list; + int proc_count; + int proc_capacity; + int remaining_procs; +} uv_proc_manager_t; + +/** + * initialize the process manager + * + * @param manager process manager pointer + * @param loop event loop pointer + * @return 0 on success, non-zero error code on failure + */ +int uv_proc_init(uv_proc_manager_t* manager, + uv_loop_t* loop); + +/** + * pre-execute a command (blocking execution, not managed by event loop) + * + * @param manager process manager pointer + * @param command pre-execute command + * @param args command arguments + * @return 0 on success, non-zero error code on failure + */ +int uv_proc_pre_exec(uv_proc_manager_t* manager, + const char* command, char* const args[]); + +/** + * asynchronously execute a command (managed by event loop) + * + * @param manager process manager pointer + * @param command command to execute + * @param args command arguments + * @param exit_cb exit callback function + * @param user_data user data passed to the callback + * @return 0 on success, non-zero error code on failure + */ +int uv_proc_exec(uv_proc_manager_t* manager, const char* command, char* const args[], + uv_proc_exit_cb exit_cb, void* user_data, int ntasks); + +/** + * post-execute a command (blocking execution, not managed by event loop) + * + * @param manager process manager pointer + * @param command post-execute command + * @param args command arguments + * @return 0 on success, non-zero error code on failure + */ +int uv_proc_post_exec(uv_proc_manager_t* manager, + const char* command, char* const args[]); + +/** + * send a signal to the current process + * + * @param manager process manager pointer + * @param signum signal number + * @return 0 on success, non-zero error code on failure + * @note this function does not wait for the process to exit + */ +int uv_proc_kill(uv_proc_manager_t* manager, int signum); + +/** + * get the current process status + * + * @param manager process manager pointer + * @return current process status + */ +uv_proc_status_t uv_proc_get_status(uv_proc_manager_t* manager); + +/** + * get the current process ID + * + * @param manager process manager pointer + * @return process ID + */ +int uv_proc_get_pid(uv_proc_manager_t* manager); + +/** + * get the last exit code + * + * @param manager process manager pointer + * @return exit code + */ +int uv_proc_get_exit_code(uv_proc_manager_t* manager); + +/** + * get the last termination signal + * + * @param manager process manager pointer + * @return termination signal + */ +int uv_proc_get_term_signal(uv_proc_manager_t* manager); + +/** + * cleanup the process manager + * + * @param manager process manager pointer + */ +void uv_proc_cleanup(uv_proc_manager_t* manager); + +/** + * wait for the process to exit + * + * @param manager process manager pointer + * @param timeout_ms timeout in milliseconds (0 for infinite wait) + * @return 0 on success, non-zero error code on failure + */ +int uv_proc_wait(uv_proc_manager_t* manager, int timeout_ms); + +/** + * pre-execute a command in background mode + * + * @param manager process manager pointer + * @param command command to execute + * @param args command arguments + * @param background whether to run in background mode (1 for yes, 0 for no) + * @return 0 on success, non-zero error code on failure + */ +int uv_proc_pre_exec_background(uv_proc_manager_t* manager, + const char* command, + char* const args[], + int background); + +/** + * execute a command in single-task mode + * + * @param manager process manager pointer + * @param command command to execute + * @param args command arguments + * @param exit_cb exit callback function + * @param user_data user data passed to the callback + * @return 0 on success, non-zero error code on failure + */ +int uv_proc_exec_single(uv_proc_manager_t* manager, + const char* command, + char* const args[], + uv_proc_exit_cb exit_cb, + void* user_data); + +#endif /* UV_PROC_H */ \ No newline at end of file diff --git a/src/worker/include/xcecd.h b/src/worker/include/xcecd.h new file mode 100644 index 00000000..7dfbe4e7 --- /dev/null +++ b/src/worker/include/xcecd.h @@ -0,0 +1,104 @@ +#ifndef XCECD_H +#define XCECD_H + +#include +#include +#include +#include "xcecd_conf.h" +#include "thread_pool.h" +#include "controller_connector.h" +#include "process_manager.h" +#include "system_monitor.h" +#include "hardware_monitor.h" +#include "resource_sync.h" +#include "node_information.h" +#include "job_information.h" +#include "xcecd_worker_utils.h" +#include "xcecd_dbus_manager.h" +#include "static_array.h" +#include "uv_ipc.h" +#include "xcecd_db.h" +#include "xcelog.h" +#include "xcectx.h" + + +#ifdef _WIN32 + #include + #include + #include + typedef HANDLE process_handle_t; +#else + #include + #include + #include + #include + #include + typedef pid_t process_handle_t; +#endif + +#define REMOTE_PORT 6789 +#define DEFAULT_PORT 0 // disable by default +#define BUFFER_SIZE 4096 +#define RESTART_FLAG_FILE "/tmp/.xcecd.restarting" + +typedef struct { + int thread_pool_size; + int resource_update_interval; + int job_status_update_interval; + uint16_t port; + const char* acct_file; + char* log_file; + const char* db_path; + const char* work_dir; + const char* domain_sock_file; + const char* remote_host; + const char* virutal_name; + int remote_port; + int join_interval; + int update_interval; + int domain_sock; + int debug_level; +} xcecd_config_t; + +typedef struct { + thread_pool_t* thread_pool; + network_handler_t* network; + process_manager_t* process_manager; + system_monitor_t* system_monitor; + hardware_monitor_t* hardware_monitor; + resource_sync_t* resource_sync; + xcecd_worker_t* worker_list; + xcecd_db_t *db; + xce_ctx_t* xce_ctx; + bool running; + bool normal_restart; + char* remote_host; + int local_port; + int remote_port; + int update_interval; + int join_interval; + int domain_sock; + int debug_level; + char* work_dir; + char* log_dir; + char* db_path; + char* virtual_name; +} xcecd_context_t; + + +extern xcecd_context_t xcecd_ctx; +extern hostattrs_t host_attrs; + +int xcecd_config_init(xcecd_config_t* config); +int xcecd_init(const xcecd_config_t* config); +int xcecd_start(void); +void xcecd_stop(void); +void xcecd_cleanup(void); +int send_status_and_receive_tasks(void); +void xcecd_process_command(void* arg); +const char* handle_network_request(const char* request, void* user_data); +bool xcecd_config_is_valid(const xcecd_config_t* config); +void xcecd_prepare_restart(void); +bool xcecd_check_restart_type(void); + +#endif // XCECD_H diff --git a/src/worker/include/xcecd_cgroups.h b/src/worker/include/xcecd_cgroups.h new file mode 100644 index 00000000..8d536ce3 --- /dev/null +++ b/src/worker/include/xcecd_cgroups.h @@ -0,0 +1,99 @@ +#ifndef _XCECD_CGROUPS_H_ +#define _XCECD_CGROUPS_H_ + +#include +#include +#include +#include +#include +#include + + +typedef struct { + // CPU limit + uint64_t cpu_quota; // CPU quota in microseconds (e.g. 200000 = 20%, cpu_limit) + uint64_t cpu_period; // CPU period in microseconds (e.g. 1000000 = 1 second) + uint32_t cpu_weight; // CPU weight (e.g. 100 = default) + uint64_t cpu_limit; // CPU limit (run time limit in seconds) + uint64_t core_limit; // Core limit (in number of cores, e.g. 2 = 2 cores) + char *cpu_affinity; // CPU affinity list (e.g. "0,1,2") + + // Memory limit + uint64_t memory_max; // Maximum memory usage (rss_limit) + uint64_t memory_high; // Memory high usage level (in bytes) + uint64_t memory_swap_max; // Maximum swap space usage (swap_limit) + + // IO limit + uint64_t io_weight; // IO weight (e.g. 100 = default) + uint64_t io_read_bps_max; // Maximum read speed (bytes/second) + uint64_t io_write_bps_max; // Maximum write speed (bytes/second) + char *io_device_path; // IO device path (e.g. "/dev/sda") + + // Tasks limit + uint64_t tasks_max; // Max number of threads (thread_limit) + uint64_t run_limit; // Max run time (in seconds) + uint64_t core_size_limit; // Max core file size (in bytes) + uint64_t data_limit; // Max data size (in bytes) + uint64_t fsize_limit; // Max file size (in bytes) + uint64_t stack_limit; // Max stack size (in bytes) + uint64_t process_limit; // Max number of processes + + // OOM score + int32_t oom_score_adjust; // OOM (-1000 to 1000) + + char *user_name; // User name +} res_limits_t; + + +/** + * start worker with resource limits + * @param command command with arguments + * @param limits resource limits (NULL if no limits) + * @param unit_name_prefix unit name prefix, NULL for default prefix + * @param job_id job id + * @param extra_args extra command line arguments (NULL if none) + * @return unit name if success, NULL if failed + */ +char* start_systemd_job(const char* command, + const res_limits_t* limits, + const char* unit_name_prefix, + uint64_t job_id, + char* extra_args); + +/** + * Get PID of a systemd job + * @param unit_name unit name + * @return PID if success, -1 if failed + */ +pid_t get_systemd_job_pid(const char* unit_name); + +/** + * Stop a systemd job + * @param unit_name unit name + * @return 0 if success, -1 if failed + */ +int stop_systemd_job(const char* unit_name); + +/** + * Check if a systemd job is active + * @param unit_name unit name + * @return true if active, false if not active + */ +bool is_systemd_job_active(const char* unit_name); + +/** + * Get systemd job exit code + * @param unit_name unit name + * @return exit code if success, -1 if failed + */ +int get_systemd_job_exit_code(const char* unit_name); + +/** + * @brief Get systemd job status text + * @param unit_name unit name + * @return status text if success, NULL if failed. + * Caller must free the returned string. + */ +char* get_systemd_job_status_text(const char* unit_name); + +#endif // _XCECD_CGROUPS_H_ \ No newline at end of file diff --git a/src/worker/include/xcecd_conf.h b/src/worker/include/xcecd_conf.h new file mode 100644 index 00000000..334d9eae --- /dev/null +++ b/src/worker/include/xcecd_conf.h @@ -0,0 +1,47 @@ +#ifndef XCECD_CONF_H +#define XCECD_CONF_H + +#include +#include +#include + +#define DEFAULT_CONFIG_PATH "/opt/xce/conf/worker.yaml" +#define MAX_HOSTNAME_LEN 256 +#define MAX_PATH_LEN 512 +#define MAX_LOG_LEVEL_LEN 16 + +typedef enum { + CONFIG_SUCCESS = 0, + CONFIG_ERROR_FILE_NOT_FOUND, + CONFIG_ERROR_PARSE_FAILED, + CONFIG_ERROR_INVALID_DATA, + CONFIG_ERROR_MEMORY, + CONFIG_ERROR_UNKNOWN +} config_error_t; + +typedef struct { + char *hostname; +} management_host_t; + +typedef struct { + management_host_t *management_list; + uint32_t management_list_count; + uint16_t controller_port; + char *log_level; + char *log_dir; +} worker_config_t; + +typedef struct { + config_error_t error_code; + char error_message[512]; + worker_config_t *config; +} config_result_t; + +config_result_t* config_load(const char *config_path); +config_result_t* config_load_default(void); +void config_free(config_result_t *result); +const char* config_error_string(config_error_t error); +void config_print(const worker_config_t *config); +int level_to_int(const char *level); + +#endif // XCECD_CONF_H diff --git a/src/worker/include/xcecd_db.h b/src/worker/include/xcecd_db.h new file mode 100644 index 00000000..abb27419 --- /dev/null +++ b/src/worker/include/xcecd_db.h @@ -0,0 +1,97 @@ +#ifndef _XCECD_DB_H +#define _XCECD_DB_H + +#include "sqlite3.h" +#include "xcecd_worker_utils.h" +#include "xcecd_utils.h" +#include +#include + + +typedef struct sqlite3 xcecd_db_t; + +/** + * @brief Initialize the database + * + * @param db Pointer to the database structure + * @param db_path Path to the database file + * @return true if success, false if failed + */ +bool xcecd_db_init(xcecd_db_t** db, + const char* db_path); + +/** + * @brief close the database + * @param db Pointer to the database structure + */ +void xcecd_db_close(xcecd_db_t* db); + +/** + * @brief Insert a worker into the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @param worker_status Status of the worker + * @param job_id Job ID associated with the worker + * @param job_status Status of the job associated with the worker + * @return true if success, false if failed + */ +bool xcecd_db_insert_worker(xcecd_db_t* db, + int worker_pid, + int worker_status, + uint64_t job_id, + int job_status); + +/** + * @brief Update a worker in the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @param worker_status Status of the worker + * @param job_status Status of the job associated with the worker + * @return true if success, false if failed + */ +bool xcecd_db_update_worker(sqlite3* db, + int worker_pid, + int worker_status, + int job_status); + +/** + * @brief Delete a worker from the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @return true if success, false if failed + */ +bool xcecd_db_delete_worker(xcecd_db_t* db, + int worker_pid); + +/** + * @brief Get a worker from the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @return worker status if success, -1 if failed + */ +int xcecd_db_get_worker(xcecd_db_t* db, + int worker_pid); + +/** + * @brief Get running workers from the database + * + * @param db Pointer to the database structure + * @return Pointer to the worker list (db->worker_list) if success, NULL if failed + */ +bool xcecd_db_get_running_workers(xcecd_db_t* db, + xcecd_worker_t** worker_list); + +/** + * @brief Check if the database is available + * @param db Pointer to the database structure + * @param db_path Path to the database file + * @return void and directly exit if the database is not available + */ +void xcecd_db_is_available(xcecd_db_t* db, + const char* db_path); + +#endif \ No newline at end of file diff --git a/src/worker/include/xcecd_dbus_manager.h b/src/worker/include/xcecd_dbus_manager.h new file mode 100644 index 00000000..64f0e251 --- /dev/null +++ b/src/worker/include/xcecd_dbus_manager.h @@ -0,0 +1,82 @@ +/** + * @file xcecd_dbus_manager.h + * @brief This file contains the declaration of the D-Bus manager functions. + * It uses to manage worker and its child process with resource limits. + */ +#ifndef _XCECD_DBUS_MANAGER_H_ +#define _XCECD_DBUS_MANAGER_H_ + +#include "sqlite3.h" +#include "xcecd_cgroups.h" +#include "xcecd_worker_utils.h" +#include "xcecd_utils.h" +#include + + +/** + * @brief Start worker with resource limits + * + * @param limits resource limits (NULL if no limits) + * @param job_id job id + * @param extra_args extra command line arguments (NULL if none) + * @return true if success, false if failed + */ +bool dbus_start_worker(const res_limits_t* limits, + uint64_t job_id, char* extra_args); + +/** + * @brief Stop a worker service + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return true if success, false if failed + */ +bool dbus_stop_worker(const char* unit_name); + +/** + * @brief Get PID of a worker service + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return PID if success, -1 if failed + */ +pid_t dbus_get_worker_pid(const char* unit_name); + +/** + * @brief Check if a worker service is active + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return true if active, false if not active + */ +bool dbus_is_worker_active(const char* unit_name); + +/** + * @brief Update worker status + * + * @param worker_list worker list + * @param job job information + * @param db database information + * @return XCECD_OK if success, others in execd_error if failed + */ +int dbus_workers_update(xcecd_worker_t** worker_list, + xcecd_job_t* job, + sqlite3* db); + +/** + * @brief Clean worker service + * + * @param worker worker information + * @param job job information + * @return true if success, false if failed + */ +bool dbus_worker_clean(xcecd_worker_t* worker, + xcecd_job_t* job); + +/** + * @brief Go through worker list and stop all workers + * @param worker_list worker list + * @param db database information + * @return void + */ +void dbus_stop_all_workers(xcecd_worker_t** worker_list, + sqlite3* db); + +#endif // _XCECD_DBUS_MANAGER_H_ \ No newline at end of file diff --git a/src/worker/include/xcecd_exdata.h b/src/worker/include/xcecd_exdata.h new file mode 100644 index 00000000..775f5d72 --- /dev/null +++ b/src/worker/include/xcecd_exdata.h @@ -0,0 +1,52 @@ +#ifndef _XCECD_EXDATA_H +#define _XCECD_EXDATA_H + +#include + +#include "xcecd_cgroups.h" +#include +#include +#include +#include +#include + +typedef struct env_var { + char* key; + char* value; + struct env_var* next; +} env_var_t; + +typedef struct { + char* local_file; + char* remote_file; + char* op; +} transfer_file_t; + +typedef struct { + transfer_file_t* ops; + int interactive_mode; + int count; +} transfer_info_t; + +typedef struct { + char* url; +} start_exdata_t; + + +bool parse_exdata(const char* exdata, + env_var_t** env_vars, + transfer_info_t* transfer_infos, + uint64_t jobid); + +bool parse_limits(const char* exdata, + res_limits_t *limits); + +void apply_env_vars(env_var_t** env_vars, + uint64_t jobid); + +void free_transfer_info(transfer_info_t* transfer_info); + +char* generate_exdata(const start_exdata_t* start_info); + + +#endif /* _XCECD_EXDATA_H */ diff --git a/src/worker/include/xcecd_utils.h b/src/worker/include/xcecd_utils.h new file mode 100644 index 00000000..5e8b76fb --- /dev/null +++ b/src/worker/include/xcecd_utils.h @@ -0,0 +1,127 @@ +#ifndef XCECD_UTILS_H +#define XCECD_UTILS_H + +#include +#include "xce/types.h" +#include "utils.h" + +/* This header file has some utilities and structure for compatibility with + * other scheduler systems. */ + +#define XCE_DEFAULT_SOCKS 15 +#define MAXPATHLEN 1024 +#define MAXHOSTNAMELEN 256 +#define MAXLINELEN 4096 +#define MAXDATASIZE (16 * 1024) +#define MAXXCENAMELEN 40 +#define MAXSRES 32 +#define MAXRESDESLEN 256 +#define NBUILTINDEX 11 +#define MAXTYPES 128 +#define MAXMODELS 128 +#define MAXTYPES_31 25 +#define MAXMODELS_31 30 +#define MAXFILENAMELEN 256 +#define MAX_CONCURRENT_JOBS 200 + +struct windows { + struct windows * nextwind; + float opentime; + float closetime; +}; + +typedef struct windows windows_t; + +enum value_type {LS_BOOLEAN, LS_NUMERIC, LS_STRING, LS_EXTERNAL}; +#define BOOLEAN LS_BOOLEAN +#define NUMERIC LS_NUMERIC +#define STRING LS_STRING +#define EXTERNAL LS_EXTERNAL +enum order_type {INCR, DECR, NA}; + +struct res_item { + char name[MAXXCENAMELEN]; + char des[MAXRESDESLEN]; + enum value_type value_type; + enum order_type order_type; + int flags; + int interval; +}; + +struct is_info { + int num_res; + struct res_item *res_table; + int num_types; + char host_types[MAXTYPES][MAXXCENAMELEN]; + int num_models; + char host_models[MAXMODELS][MAXXCENAMELEN]; + char host_archs[MAXMODELS][MAXXCENAMELEN]; + int model_refs[MAXMODELS]; + float cpu_factor[MAXMODELS]; + int num_indx; + int num_usr_indx; +}; + +#define GET_INTNUM(i) ((i)/INTEGER_BITS + 1) + +enum job_status { + JOB_PENDING, + JOB_STARTING, + JOB_RUNNING, + JOB_SUSPENDED, + JOB_DONE, + JOB_REMOVED, + JOB_EXITED, + JOB_FAILED, + JOB_UNKNOWN +}; + +enum { + WORKER_RUNNING = 0, + WORKER_DONE, + WORKER_EXITED, + WORKER_UNKNOWN +}; + +enum execd_error { + XCECD_OK = 0, + XCECD_ERROR, + XCECD_TIMEOUT, + XCECD_NO_MEMORY, + XCECD_INVALID_PARAM, + XCECD_NOT_FOUND, + XCECD_DB_ERROR, + XCECD_NOT_SUPPORTED +}; + +typedef struct { + uint64_t job_id; + int job_pid; + int job_status; + int exit_code; + int stage; + int interactive_port; + char exdata[MAXDATASIZE]; +} xcecd_job_t; + +typedef struct { + uint64_t job_id; + pid_t job_pid; + pid_t worker_pid; + int job_status; + int worker_status; + int exit_code; + int stage; + bool post_done; + int interactive_port; + char q_pre_exec[MAXLINELEN]; + char q_post_exec[MAXLINELEN]; + char job_pre_exec[MAXLINELEN]; + char job_post_exec[MAXLINELEN]; + char exdata[MAXDATASIZE]; +} job_runtime_info_t; + +void block_signal(int sig); +void unblock_signal(int sig); + +#endif diff --git a/src/worker/include/xcecd_worker_utils.h b/src/worker/include/xcecd_worker_utils.h new file mode 100644 index 00000000..22d1ef34 --- /dev/null +++ b/src/worker/include/xcecd_worker_utils.h @@ -0,0 +1,101 @@ +#ifndef XCED_WORKER_UTILS_H +#define XCED_WORKER_UTILS_H + +#include "sqlite3.h" +#include "job_information.h" +#include "xcecd_utils.h" +#include "xcelog.h" +#include +#include +#include + +struct xcecd_worker { + int worker_pid; + int worker_status; + int job_status; + int job_exit_code; + int ntasks; + char *exechome; + char *execcwd; + char *execuser; + char *job_cmd; + char *job_exdata; + char *launch_type; + char *q_pre_exec; + char *q_post_exec; + char *job_pre_exec; + char *job_post_exec; + struct xcecd_worker *prev; + struct xcecd_worker *next; + xce_job_runinfo_t job_info; +#ifdef VIRTUAL_CLUSTER + time_t start_time; + int sleep_time; + int killed_signal; +#endif +}; + +struct xcecd_db_t; +typedef struct xcecd_worker xcecd_worker_t; + + +/* TODO: New Worker states */ +typedef enum { + SERVICE_UNKNOWN = 0, + SERVICE_ACTIVE, + SERVICE_INACTIVE, + SERVICE_ACTIVATING, + SERVICE_DEACTIVATING, + SERVICE_FAILED, + SERVICE_RELOADING +} service_state_t; + +void add_worker(xcecd_worker_t** worker_list, + int worker_pid, + int worker_status, + uint64_t job_id, + int ntasks, + job_startinfo_t* startinfo); + +void remove_worker(xcecd_worker_t** worker_list, + int worker_pid); + +int update_worker_status(xcecd_worker_t* worker_list, + int worker_pid, + int worker_status, + xcecd_job_t* job, + sqlite3* db); + +/* Cleanup the worker in the list which finished work */ +void cleanup_worker_list(xcecd_worker_t** worker_list, + sqlite3* db); + +xcecd_worker_t* find_worker_by_pid(void* worker_list, + int pid); + +xcecd_worker_t* find_worker_by_jobid(xcecd_worker_t* worker_list, + uint64_t jobid); + +extern bool xcecd_reply_startjob(xcecd_job_t *job); + +extern int xcecd_job_stopped(uint64_t job_id); + +extern int xcecd_job_resumed(uint64_t job_id); + +extern int xcecd_job_finished(xcecd_job_t *job); + +extern bool xcecd_db_update_worker(sqlite3* db, + int worker_pid, + int worker_status, + int job_status); + +extern bool xcecd_db_delete_worker(sqlite3* db, + int worker_pid); + +bool resend_job_status(xcecd_worker_t* worker_list); + +#ifdef VIRTUAL_CLUSTER +bool check_worker_sleep_time_elapsed(xcecd_worker_t* worker); +#endif + +#endif // XCED_WORKER_UTILS_H diff --git a/src/worker/include/xrun.h b/src/worker/include/xrun.h new file mode 100644 index 00000000..c301c4f0 --- /dev/null +++ b/src/worker/include/xrun.h @@ -0,0 +1,20 @@ +#ifndef XRUN_H +#define XRUN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +int xrun_ipc_send_remote_start(const char *hostname, const char *command); +int run_remote_command(const char *hostname, const char *command); +void setup_signal_handlers(void); +void handle_signal(int sig); + +#endif // XRUN_H \ No newline at end of file diff --git a/src/worker/meson.build b/src/worker/meson.build new file mode 100644 index 00000000..38ea5f5f --- /dev/null +++ b/src/worker/meson.build @@ -0,0 +1,86 @@ +xcecd_worker_srcs = [ + 'src/xcecd_worker.c', + 'src/inter_job_utils.c', + 'src/xcecd_exdata.c', + 'src/uv_ipc.c', + 'src/uv_proc.c', + 'src/static_array.c' +] + +incdir = include_directories('.', 'include', '../include', '../lib') + +libuv_dep = dependency('libuv') +elogind_dep = dependency('libelogind') +sqlite3_dep = dependency('sqlite3') + + +xcecd_worker_deps = [ + libxce_dep, + libuv_dep, + elogind_dep, + sqlite3_dep, + libcjson_dep, + yaml_deps +] + +xcecd_worker = executable('xcecd_worker', + xcecd_worker_srcs, + c_args:['-DIPC_WORKER'], + include_directories: [incdir], + dependencies: [xcecd_worker_deps], + install: true) + +xrun_srcs = [ + 'src/xrun.c' +] + +xrun_deps = [libxce_dep] + +xrun = executable('xrun', + xrun_srcs, + c_args: ['-DXRUN'], + include_directories: [incdir], + dependencies: [xrun_deps], + install: true) + +xcecd_srcs = [ + 'src/static_array.c', + 'src/xcecd_conf.c', + 'src/inter_job_utils.c', + 'src/xcecd_sig.c', + 'src/uv_ipc.c', + 'src/xcecd_db.c', + 'src/xcecd_exdata.c', + 'src/xcecd_cgroups.c', + 'src/xcecd_dbus_manager.c', + 'src/main.c', + 'src/xcecd.c', + 'src/thread_pool.c', + 'src/system_monitor.c', + 'src/process_manager.c', + 'src/process_worker_manager.c', + 'src/connection_handler.c', + 'src/controller_connector.c', + 'src/resource_sync.c', + 'src/xcecd_worker_utils.c', + 'src/hardware_monitor.c', + 'src/node_information.c', + 'src/job_information.c' +] + +hwloc_dep = dependency('hwloc') + +xcecd_deps = [xcecd_worker_deps,hwloc_dep] +xcecd = executable('xcecd', + xcecd_srcs, + c_args: ['-DDBUS_MANAGER'], + include_directories: [incdir], + dependencies: [xcecd_deps], + install: true) + +xcecd_virtual = executable('xcecd_virtual', + xcecd_srcs, + c_args: ['-DVIRTUAL_CLUSTER', '-DDBUS_MANAGER'], + include_directories: [incdir], + dependencies:[xcecd_deps], + install:true) diff --git a/src/worker/scripts/startvnc b/src/worker/scripts/startvnc new file mode 100755 index 00000000..779e1e06 --- /dev/null +++ b/src/worker/scripts/startvnc @@ -0,0 +1,73 @@ +#!/bin/bash +# Script: startvnc +# Purpose: Start TigerVNC server with specified password and port +# Usage: startvnc +# Exit codes: 0 = success, 1 = invalid arguments, 2 = vnc setup failed, 3 = vnc start failed + +# Check if exactly 1 argument is provided +if [ $# -ne 1 ]; then + echo "Usage: $0 " >&2 + echo "Example: $0 5901" >&2 + exit 1 +fi + +PORT="$1" + +# Validate port number +if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1024 ] || [ "$PORT" -gt 65535 ]; then + echo "Error: Port must be a number between 1024 and 65535" >&2 + exit 1 +fi + +# Calculate display number (VNC display = port - 5900) +DISPLAY_NUM=$((PORT - 5900)) +if [ $DISPLAY_NUM -lt 1 ]; then + echo "Error: Port must be 5901 or higher for VNC display" >&2 + exit 1 +fi + +# Create VNC directory if it doesn't exist +VNC_DIR="$HOME/.vnc" +mkdir -p "$VNC_DIR" + +# Create xstartup file if it doesn't exist +XSTARTUP_FILE="$VNC_DIR/xstartup" +if [ ! -f "$XSTARTUP_FILE" ]; then + cat > "$XSTARTUP_FILE" << 'EOF' +#!/bin/sh + +unset SESSION_MANAGER +unset DBUS_SESSION_BUS_ADDRESS + +[ -f ~/.Xresources ] && xrdb ~/.Xresources +xsetroot -solid grey +vncconfig -iconic & + +# Start Xfce4 desktop +if type startxfce4 >/dev/null 2>&1; then + startxfce4 +elif type xfce4-session >/dev/null 2>&1; then + xfce4-session +fi +EOF + chmod +x "$XSTARTUP_FILE" +fi + +# Kill any existing VNC server on this display +vncserver -kill ":$DISPLAY_NUM" 2>/dev/null || true + +# Start VNC server (remove -passwd parameter as password file is already set) +vncserver ":$DISPLAY_NUM" -PlainUsers=$USER -pam_service=login -SecurityTypes=TLSPlain +if [ $? -ne 0 ]; then + echo "Error: Failed to start VNC server on port $PORT (display :$DISPLAY_NUM)" >&2 + exit 3 +fi + +# Verify the server is running +sleep 2 +if ! netstat -ln | grep -q ":$PORT "; then + echo "Warning: VNC server may not be listening on port $PORT" >&2 +fi + +echo "VNC server started successfully on port $PORT (display :$DISPLAY_NUM)" +echo "Password file: $VNC_DIR/passwd" \ No newline at end of file diff --git a/src/worker/scripts/stopvnc b/src/worker/scripts/stopvnc new file mode 100755 index 00000000..61ae1a41 --- /dev/null +++ b/src/worker/scripts/stopvnc @@ -0,0 +1,85 @@ +#!/bin/bash + +# Script: stopvnc +# Purpose: Stop TigerVNC server running on specified port +# Usage: stopvnc +# Exit codes: 0 = success, 1 = invalid arguments, 2 = vnc server not found, 3 = stop failed + +# Check if exactly 1 argument is provided +if [ $# -ne 1 ]; then + echo "Usage: $0 " >&2 + echo "Example: $0 5900" >&2 + exit 1 +fi + +PORT="$1" + +# Validate port number +if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1024 ] || [ "$PORT" -gt 65535 ]; then + echo "Error: Port must be a number between 1024 and 65535" >&2 + exit 1 +fi + +# Calculate display number (VNC display = port - 5900) +DISPLAY_NUM=$((PORT - 5900)) +if [ $DISPLAY_NUM -lt 0 ]; then + echo "Error: Port must be 5900 or higher for VNC display" >&2 + exit 1 +fi + +# Check if VNC server is running on this display +VNC_LOCK_FILE="$HOME/.vnc/$(hostname):${DISPLAY_NUM}.pid" +if [ ! -f "$VNC_LOCK_FILE" ]; then + # Also check if process is actually running on the port + if ! netstat -tuln 2>/dev/null | grep -q ":$PORT "; then + echo "No VNC server found running on port $PORT (display :$DISPLAY_NUM)" >&2 + exit 2 + fi +fi + +# Try to kill VNC server gracefully +echo "Stopping VNC server on port $PORT (display :$DISPLAY_NUM)..." +vncserver -kill ":$DISPLAY_NUM" +KILL_RESULT=$? + +# Check if the kill command succeeded +if [ $KILL_RESULT -eq 0 ]; then + echo "VNC server on port $PORT stopped successfully" + exit 0 +else + # If graceful shutdown failed, try to find and kill the process by port + echo "Graceful shutdown failed, attempting forceful termination..." + + # Find process using the port + PID=$(netstat -tulnp 2>/dev/null | grep ":$PORT " | awk '{print $7}' | cut -d'/' -f1) + + if [ -n "$PID" ] && [ "$PID" != "-" ]; then + kill -TERM "$PID" 2>/dev/null + sleep 2 + + # Check if process is still running + if kill -0 "$PID" 2>/dev/null; then + # Force kill if still running + kill -KILL "$PID" 2>/dev/null + sleep 1 + fi + + # Verify the process is gone + if ! kill -0 "$PID" 2>/dev/null; then + echo "VNC server on port $PORT forcefully terminated" + + # Clean up lock file if it exists + if [ -f "$VNC_LOCK_FILE" ]; then + rm -f "$VNC_LOCK_FILE" + fi + + exit 0 + else + echo "Error: Failed to stop VNC server on port $PORT" >&2 + exit 3 + fi + else + echo "Error: Could not find process using port $PORT" >&2 + exit 2 + fi +fi \ No newline at end of file diff --git a/src/worker/src/connection_handler.c b/src/worker/src/connection_handler.c new file mode 100644 index 00000000..d40c5588 --- /dev/null +++ b/src/worker/src/connection_handler.c @@ -0,0 +1,239 @@ +#include "connection_handler.h" +#include "xcelog.h" +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "ws2_32.lib") +#else +#include +#include +#include +#include +#include +#endif + +#define BUFFER_SIZE 1024 +#define MAX_PENDING 10 + +struct network_handler { + uint16_t port; + bool running; + request_handler_t callback; + void* user_data; +#ifdef _WIN32 + SOCKET server_socket; + HANDLE thread; +#else + int server_socket; + pthread_t thread; +#endif +}; + +#ifdef _WIN32 +static DWORD WINAPI server_thread(LPVOID arg) +#else +static void* server_thread(void* arg) +#endif +{ + network_handler_t* handler = (network_handler_t*)arg; + char buffer[BUFFER_SIZE]; + + while (handler->running) { +#ifdef _WIN32 + SOCKET client_socket = accept(handler->server_socket, NULL, NULL); + if (client_socket == INVALID_SOCKET) { + continue; + } +#else + int client_socket = accept(handler->server_socket, NULL, NULL); + if (client_socket < 0) { + continue; + } +#endif + + // 读取请求 + memset(buffer, 0, BUFFER_SIZE); +#ifdef _WIN32 + int bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0); + if (bytes_received != SOCKET_ERROR && handler->callback) { +#else + int bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0); + if (bytes_received > 0 && handler->callback) { +#endif + // 调用回调处理请求,并获取响应 + const char* result = handler->callback(buffer, handler->user_data); + + // 发送响应给客户端 + if (result) { +#ifdef _WIN32 + send(client_socket, result, (int)strlen(result), 0); +#else + send(client_socket, result, strlen(result), 0); +#endif + free((void*)result); // 释放回调函数分配的内存 + } else { + const char* error_msg = "Command execution failed\n"; +#ifdef _WIN32 + send(client_socket, error_msg, (int)strlen(error_msg), 0); +#else + send(client_socket, error_msg, strlen(error_msg), 0); +#endif + } + } + +#ifdef _WIN32 + closesocket(client_socket); +#else + close(client_socket); +#endif + } + + return 0; +} + +network_handler_t* network_handler_create(uint16_t port) { + network_handler_t* handler = calloc(1, sizeof(network_handler_t)); + if (!handler) { + return NULL; + } + + handler->port = port; + +#ifdef _WIN32 + // 初始化Winsock + WSADATA wsa_data; + if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { + free(handler); + return NULL; + } +#endif + + return handler; +} + +void network_handler_set_callback(network_handler_t* handler, request_handler_t callback, void* user_data) { + if (handler) { + handler->callback = callback; + handler->user_data = user_data; + } +} + +int network_handler_start(network_handler_t* handler) { + if (!handler) { + return -1; + } + + // 创建服务器socket +#ifdef _WIN32 + handler->server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (handler->server_socket == INVALID_SOCKET) { + return -1; + } +#else + handler->server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (handler->server_socket < 0) { + return -1; + } +#endif + + // 设置socket选项 + int opt = 1; +#ifdef _WIN32 + if (setsockopt(handler->server_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) == SOCKET_ERROR) { + closesocket(handler->server_socket); + return -1; + } +#else + if (setsockopt(handler->server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + close(handler->server_socket); + return -1; + } +#endif + + // 绑定地址 + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(handler->port); + +#ifdef _WIN32 + if (bind(handler->server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) { + closesocket(handler->server_socket); + return -1; + } +#else + if (bind(handler->server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + close(handler->server_socket); + return -1; + } +#endif + + // 开始监听 +#ifdef _WIN32 + if (listen(handler->server_socket, MAX_PENDING) == SOCKET_ERROR) { + closesocket(handler->server_socket); + return -1; + } +#else + if (listen(handler->server_socket, MAX_PENDING) < 0) { + close(handler->server_socket); + return -1; + } +#endif + + handler->running = true; + + // 创建服务器线程 +#ifdef _WIN32 + handler->thread = CreateThread(NULL, 0, server_thread, handler, 0, NULL); + if (!handler->thread) { + closesocket(handler->server_socket); + return -1; + } +#else + if (pthread_create(&handler->thread, NULL, server_thread, handler) != 0) { + close(handler->server_socket); + return -1; + } +#endif + + return 0; +} + +void network_handler_stop(network_handler_t* handler) { + if (!handler) { + return; + } + + handler->running = false; + +#ifdef _WIN32 + closesocket(handler->server_socket); + WaitForSingleObject(handler->thread, INFINITE); + CloseHandle(handler->thread); +#else + close(handler->server_socket); + pthread_join(handler->thread, NULL); +#endif +} + +void network_handler_destroy(network_handler_t* handler) { + if (!handler) { + return; + } + + if (handler->running) { + network_handler_stop(handler); + } + +#ifdef _WIN32 + WSACleanup(); +#endif + + free(handler); +} diff --git a/src/worker/src/controller_connector.c b/src/worker/src/controller_connector.c new file mode 100755 index 00000000..063e6a15 --- /dev/null +++ b/src/worker/src/controller_connector.c @@ -0,0 +1,1561 @@ +#include "controller_connector.h" +#include "netio.h" +#include "uv_ipc.h" +#include "xcecd_worker_utils.h" +#include "xcecd_exdata.h" +#include "xcecd_cgroups.h" +#include "xcelog.h" +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#ifdef XRUN +hostattrs_t host_attrs = { 0 }; +#else +extern hostattrs_t host_attrs; +#endif + +xcecli_ctx_t xcecli_ctx = { 0 }; +xce_client_config_t xce_client_cfg = { 0 }; +controller_connector_ctx_t* cc_ctx = NULL; + +static int myreq_handler(xce_request_t*); + +/* make compiler happy */ +typedef struct { + task_func_t function; + void* arg; +} thread_wrapper_t; + +static const cc_callbacks_t cc_callbacks = { + .job_finished = xcecd_job_finished, + .job_stopped = xcecd_job_stopped, + .job_resumed = xcecd_job_resumed, + .job_start_error = xcecd_job_start_error, + .job_status_error = xcecd_job_status_error, + .job_ckpted = xcecd_job_ckpted, + .job_remote_start = xcecd_job_remote_start, + .reply_startjob = xcecd_reply_startjob +}; + +static void free_startinfo(job_startinfo_t* info) +{ + if (!info) { + return; + } + FREEUP(info->cmdline); + FREEUP(info->execuser); + FREEUP(info->exdata); + FREEUP(info->exechome); + FREEUP(info->execcwd); + FREEUP(info->q_pre_exec); + FREEUP(info->q_post_exec); + FREEUP(info->job_pre_exec); + FREEUP(info->job_post_exec); + FREEUP(info); +} + +static void free_limits(res_limits_t* limits) +{ + if (!limits) { + return; + } + FREEUP(limits->cpu_affinity); + FREEUP(limits->io_device_path); + FREEUP(limits->user_name); + FREEUP(limits); +} + +/** + * @brief Initializes the host attributes with default values. + */ +const cc_callbacks_t* get_cc_callbacks(void) +{ + return &cc_callbacks; +} + +static void* thread_start_wrapper(void* arg) +{ + thread_wrapper_t* wrapper = (thread_wrapper_t*)arg; + wrapper->function(wrapper->arg); + FREEUP(wrapper); + return NULL; +} + +/* my old implementation and currently is disabled */ +int start_controller_connector_thread(task_func_t function, void* arg) +{ + XCECD_LOG(XCE_LOG_DEBUG, "Starting controller connector thread..."); +#ifdef _WIN32 + HANDLE status_thread = CreateThread(NULL, + 0, + (LPTHREAD_START_ROUTINE)function, + arg, + 0, + NULL); + if (status_thread == NULL) { + return -1; + } + CloseHandle(status_thread); +#else + pthread_t status_thread; + thread_wrapper_t* wrapper = calloc(1, sizeof(thread_wrapper_t)); + if (!wrapper) { + return -1; + } + wrapper->function = function; + wrapper->arg = arg; + if (pthread_create(&status_thread, + NULL, + thread_start_wrapper, + wrapper) + != 0) { + FREEUP(wrapper); + return -1; + } + pthread_detach(status_thread); +#endif + + XCECD_LOG(XCE_LOG_DEBUG, "Controller connector thread started."); + return 0; +} + +/** + * @brief Initializes the client configuration for the controller connector. + * + * @param config Pointer to the xce_client_config_t structure to be initialized. + */ +static void init_client_config(xce_client_config_t* config) +{ + int ret; + char local_host[MAXHOSTNAMELEN] = { 0 }; + + if (!config) { + return; + } + + ret = gethostname(local_host, sizeof(local_host)); + if (ret < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "gethostname failed: %s", + strerror(errno)); + strncpy(local_host, "localhost", sizeof(local_host) - 1); + } + + config->nmasters = 1; + config->masters[0] = cc_ctx->remote_host; + config->master_ports[0] = cc_ctx->remote_port; +#ifdef VIRTUAL_CLUSTER + config->from = strdup(cc_ctx->virtual_name); +#else + config->from = strdup(local_host); +#endif + config->key = strdup("keepSecret"); + + XCECD_LOG(XCE_LOG_DEBUG, "Controller connector configuration initialized."); + XCECD_LOG(XCE_LOG_DEBUG, + "Join cluster configuration - Local host: %s, Remote host: %s, Remote port: %d, Key: %s", + config->from, + config->masters[0], + config->master_ports[0], + config->key); +} + +static void handle_disconnect() +{ + XCECD_LOG(XCE_LOG_DEBUG, "Handling disconnect and shutdown XCECD..."); + + // Shutdown worker and waiting for restart + cc_ctx->running = false; + + XCECD_LOG(XCE_LOG_DEBUG, "Disconnect handled."); +} + +/** + * @brief Handles I/O events for the endpoint, focusing on error conditions + * and connection liveness checks using EPOLLIN/EPOLLOUT. + * + * @return int Always returns 0. + */ +int checking_connection() +{ + const int MAX_RETRY_COUNT = 5; + const int RETRY_INTERVAL = 5; // seconds + int retry_count = 0; + int ret; + + while (retry_count < MAX_RETRY_COUNT) { + ret = xcecli_hello(xcecli_ctx, "hello"); + if (ret == 0) { + // Connection successful + if (retry_count > 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "Connection to controller restored after %d retries", + retry_count); + } + return 0; + } + + retry_count++; + XCECD_LOG(XCE_LOG_ERROR, + "Connection to controller is broken. Retry %d/%d failed", + retry_count, MAX_RETRY_COUNT); + + if (0 == xcecd_join_cluster(&host_attrs)) { + XCECD_LOG(XCE_LOG_INFO, + "Rejoined cluster successfully after retry %d", + retry_count); + + // api_ctx_t* api_ctx = (api_ctx_t*)xcecli_ctx; + // cc_ctx->xce_ctx = &(api_ctx->ctx); + return 0; + } + + if (retry_count < MAX_RETRY_COUNT) { + XCECD_LOG(XCE_LOG_INFO, + "Retrying connection in %d seconds...", + RETRY_INTERVAL); + sleep(RETRY_INTERVAL); + } + } + + // All retries exhausted + XCECD_LOG(XCE_LOG_ERROR, + "Connection to controller failed after %d retries. Handling disconnect...", + MAX_RETRY_COUNT); + handle_disconnect(); + + return 0; +} + +/** + * @brief Joins the cluster using the provided host attributes. + * + * @param attrs Pointer to the host attributes structure. + * @return int 0 on success, -1 on failure. + */ +int xcecd_join_cluster(hostattrs_t* attrs) +{ + if (!attrs) { + return -1; + } + + int rc; + int role = HostRole_worker; + + xcecli_ctx_init(&xcecli_ctx, &xce_client_cfg); + xcecli_set_handler(xcecli_ctx, &myreq_handler); + xcecli_start(xcecli_ctx); + + rc = xcecli_join(xcecli_ctx, role, attrs); + + if (rc < 0) { + errno = -rc; + XCECD_LOG(XCE_LOG_ERROR, + "Call xcecli_join() failure: %s", + strerror(errno)); + return rc; + } + + return 0; +} + +#ifndef XRUN +static void dump_startjob_request(xce_job_exec_t* d) +{ + if (!d) { + return; + } + + XCECD_LOG(XCE_LOG_DEBUG, + "Job ID: %lu, Command: %s, Exec User: %s, Exdata: %s", + d->jobid, + d->start_info->cmdline ? d->start_info->cmdline : "NULL", + d->start_info->execuser ? d->start_info->execuser : "NULL", + d->start_info->exdata ? d->start_info->exdata : "NULL"); +} +#endif // XRUN + +static char* create_mpi_cmdline(char* cmd, char* type, + xce_launch_t* launchinfo) +{ + if (!cmd || !type || !launchinfo || launchinfo->nhosts <= 0) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid parameters for create_mpi_cmdline"); + return NULL; + } + + char* mpi_executable = NULL; + char* app_command = NULL; + char* token = NULL; + char* cmd_copy = strdup(cmd); + if (!cmd_copy) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for cmd_copy"); + return NULL; + } + + // Parse the original command to extract mpi executable path and application command + token = strtok(cmd_copy, " "); + if (!token) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to parse command: %s", cmd); + FREEUP(cmd_copy); + return NULL; + } + + // Check if first token contains mpirun or mpiexec + if (strstr(token, "mpirun") || strstr(token, "mpiexec")) { + mpi_executable = strdup(token); + // Get the rest of the command as application + char* rest = cmd + strlen(token); + while (*rest == ' ') rest++; // skip spaces + app_command = strdup(rest); + } else { + XCECD_LOG(XCE_LOG_ERROR, "Command does not contain mpirun or mpiexec: %s", cmd); + FREEUP(cmd_copy); + return NULL; + } + + FREEUP(cmd_copy); + + if (!mpi_executable || !app_command) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to extract mpi executable or application command"); + FREEUP(mpi_executable); + FREEUP(app_command); + return NULL; + } + + // Calculate the size needed for host list + size_t hostlist_size = 0; + for (int i = 0; i < launchinfo->nhosts; i++) { + if (launchinfo->hosts[i] && launchinfo->hosts[i]->host) { + // host:nrank + comma + null terminator + hostlist_size += strlen(launchinfo->hosts[i]->host) + 20; // 20 should be enough for :nrank + } + } + + if (hostlist_size == 0) { + XCECD_LOG(XCE_LOG_ERROR, "No valid hosts found in launch info"); + FREEUP(mpi_executable); + FREEUP(app_command); + return NULL; + } + + // Build host list string + char* hostlist = calloc(hostlist_size, sizeof(char)); + if (!hostlist) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for hostlist"); + FREEUP(mpi_executable); + FREEUP(app_command); + return NULL; + } + + for (int i = 0; i < launchinfo->nhosts; i++) { + if (launchinfo->hosts[i] && launchinfo->hosts[i]->host) { + if (strlen(hostlist) > 0) { + strcat(hostlist, ","); + } + strcat(hostlist, launchinfo->hosts[i]->host); + strcat(hostlist, ":"); + + char nrank_str[16]; + snprintf(nrank_str, sizeof(nrank_str), "%d", launchinfo->hosts[i]->nranks); + strcat(hostlist, nrank_str); + } + } + + char launcher[MAXFILENAMELEN] = {0}; + char* server_dir = getenv("XCE_SERVER_DIR"); + if (server_dir) { + snprintf(launcher, sizeof(launcher), "%s/xrun", server_dir); + } else { + snprintf(launcher, sizeof(launcher), "/opt/xce/bin/xrun"); + } + + if (access(launcher, X_OK) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "xrun launcher not found or not executable at %s", launcher); + FREEUP(mpi_executable); + FREEUP(app_command); + FREEUP(hostlist); + return NULL; + } + + // Determine the host parameter based on MPI type + const char* host_param = NULL; + const char* launcher_param = NULL; + if (strcmp(type, "openmpi") == 0) { + host_param = "-host"; + launcher_param = "--mca plm_rsh_agent"; + } else if (strcmp(type, "mpich") == 0) { + host_param = "-hosts"; + launcher_param = "-launcher-exec"; + } else { + XCECD_LOG(XCE_LOG_ERROR, "Unsupported MPI type: %s", type); + FREEUP(mpi_executable); + FREEUP(app_command); + FREEUP(hostlist); + return NULL; + } + + // Calculate final command size + size_t final_cmd_size = strlen(mpi_executable) + 1 + // executable + space + strlen(host_param) + 1 + // host parameter + space + strlen(hostlist) + 1 + // hostlist + space + strlen(launcher_param) + 1 + // launcher parameter + space + strlen(launcher) + 1 + // launcher + space + strlen(app_command) + 1; // application + null terminator + + char* final_cmd = calloc(final_cmd_size, sizeof(char)); + if (!final_cmd) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for final command"); + FREEUP(mpi_executable); + FREEUP(app_command); + FREEUP(hostlist); + return NULL; + } + + // Build the final command + snprintf(final_cmd, final_cmd_size, "%s %s %s %s %s %s", + mpi_executable, host_param, hostlist, + launcher_param, launcher, app_command); + + XCECD_LOG(XCE_LOG_DEBUG3, "Generated MPI command: %s", final_cmd); + + // Cleanup + FREEUP(mpi_executable); + FREEUP(app_command); + FREEUP(hostlist); + + return final_cmd; +} + +/** + * @brief Runs a job task on remote hosts. + * + * @param jobid The ID of the job to run. + * @param startinfo Pointer to the job start information. + * @param launchinfo Pointer to the job launch information. + * @return int 0 on success, -1 on failure. + */ +static int job_start_remote_task(uint64_t jobid, + job_startinfo_t* startinfo, + xce_launch_t* launchinfo) +{ + xce_taskhost_t hosts[launchinfo->nhosts - 1]; + xce_taskrun_t taskrun; + int i; + int rc; + + if (!startinfo || !launchinfo) { + XCECD_LOG(XCE_LOG_ERROR, + "Invalid parameters for job_run_task_remote"); + return -1; + } + + if ((strcmp(startinfo->launch_type, "openmpi") == 0) || + (strcmp(startinfo->launch_type, "mpich") == 0)) { + XCECD_LOG(XCE_LOG_DEBUG3, + "MPI launch detected, using MPI task run method."); + return 0; + } + + for (i = 1; i < launchinfo->nhosts; i++) { + hosts[i - 1].exechome = startinfo->exechome; + hosts[i - 1].execcwd = startinfo->execcwd; + hosts[i - 1].cmdline = startinfo->cmdline; + hosts[i - 1].exdata = startinfo->exdata; + hosts[i - 1].execuser = startinfo->execuser; + hosts[i - 1].launch_type = startinfo->launch_type; + + hosts[i - 1].host = launchinfo->hosts[i]->host; + hosts[i - 1].ntasks = launchinfo->hosts[i]->nranks; + + XCECD_LOG(XCE_LOG_DEBUG3, + "Remote task setup for host %s with %d ranks", + hosts[i - 1].host, hosts[i - 1].ntasks); + } + + taskrun.taskhosts = (xce_taskhost_t**)calloc(launchinfo->nhosts - 1, + sizeof(xce_taskhost_t*)); + taskrun.clientidx = -1; + + if (taskrun.taskhosts != NULL) { + for (i = 1; i < launchinfo->nhosts; i++) { + taskrun.taskhosts[i - 1] = &hosts[i - 1]; + } + + taskrun.nhosts = launchinfo->nhosts - 1; + taskrun.jobid = jobid; + + XCECD_LOG(XCE_LOG_DEBUG, + "Launching %d remote tasks for job %lu", + taskrun.nhosts, jobid); + + xcecli_unblock_tasks(xcecli_ctx); + rc = xcecli_runtask(xcecli_ctx, &taskrun); + if (rc < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Job %lu run remote task failed.", jobid); + } + } else { + rc = -ENOMEM; + } + + FREEUP(taskrun.taskhosts); + + return rc; +} + +/** + * @brief Starts a job task on the local host. + * + * @param request Pointer to the xce_request_t structure containing the request data. + * @return int 0 on success, -1 on failure. + */ +static int job_start_local_task(xce_request_t* request) +{ +#ifndef XRUN + xce_job_exec_t* d = request->body.jobstartmsg; + + dump_startjob_request(d); + + fork_task_t* job_ctx = calloc(1, sizeof(fork_task_t)); + if (job_ctx) { + job_ctx->startinfo = calloc(1, sizeof(job_startinfo_t)); + if (!job_ctx->startinfo) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for startinfo"); + xcecd_job_start_error(d->jobid); + FREEUP(job_ctx); + return -1; + } + + if (d->start_info->execuser && d->start_info->execuser[0] != '\0') { + job_ctx->startinfo->execuser = strdup((char*)d->start_info->execuser); + } else { + job_ctx->startinfo->execuser = strdup("nobody"); + XCECD_LOG(XCE_LOG_WARN, + "No execuser provided in startjob request, defaulting to 'nobody'"); + } + + if (!job_ctx->startinfo->execuser) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for execuser"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + +#if 0 + char data[1024] = {0}; + snprintf(data, sizeof(data), + "{\"sub_env_vars\": {\"XCE_TEST1\": \"abab\", \"XCE_TEST2\": \"cdcd\"}, \"transfer_file\": [{\"op\": \">\", \"local_file\": \"/tmp/file1.txt\", \"remote_file\": \"\"}, {\"op\": \">\", \"local_file\": \"/tmp/file2.txt\", \"remote_file\": \"\"}], \"interactive\": 1}"); + job_ctx->exdata = strdup(data); +#else + + if (!d->start_info->exdata || d->start_info->exdata[0] == '\0') { + job_ctx->startinfo->exdata = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No exdata provided in startjob request"); + } else { + job_ctx->startinfo->exdata = strdup((char*)d->start_info->exdata); + XCECD_LOG(XCE_LOG_DEBUG3, + "Received exdata: %s", + job_ctx->startinfo->exdata); + + if (!job_ctx->startinfo->exdata) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for exdata"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } +#endif + + if (!d->start_info->exechome || + d->start_info->exechome[0] == '\0') { + job_ctx->startinfo->exechome = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No exechome provided in startjob request"); + } else { + job_ctx->startinfo->exechome = strdup((char*)d->start_info->exechome); + if (!job_ctx->startinfo->exechome) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for exechome"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } + + if (!d->start_info->execcwd || + d->start_info->execcwd[0] == '\0') { + job_ctx->startinfo->execcwd = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No execcwd provided in startjob request"); + } else { + job_ctx->startinfo->execcwd = strdup((char*)d->start_info->execcwd); + if (!job_ctx->startinfo->execcwd) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for execcwd"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } + + if (!d->start_info->q_pre_exec || + d->start_info->q_pre_exec[0] == '\0') { + job_ctx->startinfo->q_pre_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No queue level pre-exec provided in startjob request"); + } else { + job_ctx->startinfo->q_pre_exec = strdup((char*)d->start_info->q_pre_exec); + if (!job_ctx->startinfo->q_pre_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for queue level pre-exec command."); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } + + if (!d->start_info->q_post_exec || + d->start_info->q_post_exec[0] == '\0') { + job_ctx->startinfo->q_post_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No queue level post-exec provided in startjob request"); + } else { + job_ctx->startinfo->q_post_exec = strdup((char*)d->start_info->q_post_exec); + if (!job_ctx->startinfo->q_post_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for queue level post-exec command."); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } + + if (!d->start_info->job_pre_exec || + d->start_info->job_pre_exec[0] == '\0') { + job_ctx->startinfo->job_pre_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No job level pre-exec provided in startjob request"); + } else { + job_ctx->startinfo->job_pre_exec = strdup((char*)d->start_info->job_pre_exec); + if (!job_ctx->startinfo->job_pre_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for job level pre-exec command."); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } + + if (!d->start_info->job_post_exec || + d->start_info->job_post_exec[0] == '\0') { + job_ctx->startinfo->job_post_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No job level post-exec provided in startjob request"); + } else { + job_ctx->startinfo->job_post_exec = strdup((char*)d->start_info->job_post_exec); + if (!job_ctx->startinfo->job_post_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for job level post-exec command."); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } + +#ifndef DBUS_MANAGER + job_ctx->manager = (void*)cc_ctx->process_manager; +#endif + + if (d->start_info->launch_type) { + job_ctx->startinfo->launch_type = strdup((char*)d->start_info->launch_type); + } else { + job_ctx->startinfo->launch_type = NULL; + } + + job_ctx->func = xcecd_process_command; + job_ctx->xce_jobid = d->jobid; + job_ctx->ntasks = d->launchinfo->hosts[0]->nranks; + job_ctx->clientidx = -1; + + /* Create MPI command line */ + if (strcmp(job_ctx->startinfo->launch_type, "openmpi") == 0 || + strcmp(job_ctx->startinfo->launch_type, "mpich") == 0) { + job_ctx->startinfo->cmdline = create_mpi_cmdline(d->start_info->cmdline, + d->start_info->launch_type, + d->launchinfo); + if (job_ctx->startinfo->cmdline == NULL) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to create MPI command line"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + + /* In MPI case, we set ntasks to 1 on all nodes as we have passed + * the host options command line to all of them. + */ + job_ctx->ntasks = 1; + } + else { + job_ctx->startinfo->cmdline = strdup((char*)d->start_info->cmdline); + if (!job_ctx->startinfo->cmdline) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for command"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + } + + XCECD_LOG(XCE_LOG_DEBUG, + "Job %lu ntasks is: %d", + d->jobid, job_ctx->ntasks); + + /* Job limits */ + job_ctx->limits = (res_limits_t*)calloc(1, sizeof(res_limits_t)); + if (!job_ctx->limits) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for job limits"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + FREEUP(job_ctx); + return -1; + } + + /* Even exdata is empty, we still need to initialize limits as for runtime user */ + if (!parse_limits(job_ctx->startinfo->exdata, job_ctx->limits)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to parse job limits from exdata"); + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + free_limits(job_ctx->limits); + FREEUP(job_ctx); + return -1; + } + + if (job_ctx->startinfo->execuser != NULL) { + job_ctx->limits->user_name = strdup(job_ctx->startinfo->execuser); + } else { + job_ctx->limits->user_name = strdup("nobody"); + XCECD_LOG(XCE_LOG_WARN, + "No execuser provided, defaulting to 'nobody'"); + } + + job_ctx->limits->cpu_affinity = NULL; + job_ctx->limits->io_device_path = NULL; + + if (submit_fork_task(cc_ctx->thread_pool, job_ctx) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to add task to thread pool"); + + xcecd_job_start_error(d->jobid); + free_startinfo(job_ctx->startinfo); + free_limits(job_ctx->limits); + FREEUP(job_ctx); + return -1; + } else { + XCECD_LOG(XCE_LOG_DEBUG, + "Task added to thread pool queue"); + } + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for task context"); + xcecd_job_start_error(d->jobid); + return -1; + } +#endif + return 0; +} + +static int handle_startjob(xce_request_t* request) { + int rc = 0; + + XCECD_LOG(XCE_LOG_DEBUG, + "Received startjob request from controller"); + + if (request->body.jobstartmsg == NULL || + request->body.jobstartmsg->start_info == NULL || + request->body.jobstartmsg->launchinfo == NULL) { + XCECD_LOG(XCE_LOG_ERROR, + "Received startjob request with invalid parameters"); + return -1; + } + + if (request->body.jobstartmsg->jobid <= 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Received startjob request with invalid job ID"); + return -1; + } + + uint64_t jobid = request->body.jobstartmsg->jobid; + + if (request->body.jobstartmsg->start_info->cmdline == NULL) { + XCECD_LOG(XCE_LOG_ERROR, + "Received startjob request with empty command"); + xcecd_job_start_error(jobid); + return -1; + } + + if (request->body.jobstartmsg->start_info->execuser == NULL) { + XCECD_LOG(XCE_LOG_ERROR, + "Received startjob request with empty execuser"); + xcecd_job_start_error(jobid); + return -1; + } + + job_startinfo_t* startinfo = request->body.jobstartmsg->start_info; + xce_launch_t* launchinfo = request->body.jobstartmsg->launchinfo; + + if (launchinfo->nhosts > 1) { + rc = job_start_remote_task(jobid, startinfo, launchinfo); + if (rc < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Job %lu run remote task failed, rc %d.", + jobid, rc); + xcecd_task_fail(jobid, request->body.runtaskmsg->clientidx, -1, rc); + return rc; + } + } + + rc = job_start_local_task(request); + + return rc; +} + +/** + * @brief Handles the runtasks request from the controller. + * + * @param request Pointer to the xce_request_t structure containing the request data. + * @return int 0 on success, -1 on failure. + */ +static int handle_runtasks(xce_request_t* request) +{ +#ifndef XRUN + XCECD_LOG(XCE_LOG_DEBUG, + "Received runtasks request from controller"); + + xce_taskrun_t* runmsg = request->body.runtaskmsg; + + if (!runmsg) { + XCECD_LOG(XCE_LOG_ERROR, + "Received runtasks request with invalid message"); + return -1; + } + + if (runmsg->jobid <= 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Received runtasks request with invalid job ID"); + return -1; + } + + if (runmsg->nhosts != 1) { + XCECD_LOG(XCE_LOG_ERROR, + "Received runtasks request with invalid number of hosts: %d", + runmsg->nhosts); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -EINVAL); + return -1; + } + + if (runmsg->taskhosts[0]->host == NULL) { + XCECD_LOG(XCE_LOG_ERROR, + "Received runtasks request with empty host name"); + return -1; + } + + if (strcmp(xce_client_cfg.from, runmsg->taskhosts[0]->host) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Received runtasks request for a different host: %s", + runmsg->taskhosts[0]->host); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -EINVAL); + return -1; + } + + XCECD_LOG(XCE_LOG_DEBUG3, + "run task: jobid = %lu", runmsg->jobid); + XCECD_LOG(XCE_LOG_DEBUG3, + "run task: user = %s", + runmsg->taskhosts[0]->execuser ? runmsg->taskhosts[0]->execuser : "NULL"); + XCECD_LOG(XCE_LOG_DEBUG3, + "run task: cmdline = %s", + runmsg->taskhosts[0]->cmdline ? runmsg->taskhosts[0]->cmdline : "NULL"); + XCECD_LOG(XCE_LOG_DEBUG3, + "run task: exdata = %s", + runmsg->taskhosts[0]->exdata ? runmsg->taskhosts[0]->exdata : "NULL"); + XCECD_LOG(XCE_LOG_DEBUG3, + "run task: ntasks = %d", runmsg->taskhosts[0]->ntasks); + XCECD_LOG(XCE_LOG_DEBUG3, + "run task: exechome = %s", + runmsg->taskhosts[0]->exechome ? runmsg->taskhosts[0]->exechome : "NULL"); + XCECD_LOG(XCE_LOG_DEBUG3, + "run task: execcwd = %s", + runmsg->taskhosts[0]->execcwd ? runmsg->taskhosts[0]->execcwd : "NULL"); + + fork_task_t* task_ctx = calloc(1, sizeof(fork_task_t)); + if (task_ctx) { + task_ctx->startinfo = calloc(1, sizeof(job_startinfo_t)); + if (!task_ctx->startinfo) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for startinfo"); + FREEUP(task_ctx); + return -1; + } + + task_ctx->startinfo->cmdline = strdup((char*)runmsg->taskhosts[0]->cmdline); + if (!task_ctx->startinfo->cmdline) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for command"); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + + task_ctx->startinfo->execuser = strdup((char*)runmsg->taskhosts[0]->execuser); + if (!task_ctx->startinfo->execuser) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for execuser"); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + + task_ctx->ntasks = runmsg->taskhosts[0]->ntasks; + if (task_ctx->ntasks <= 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Received runtasks request with invalid number of tasks: %d", + task_ctx->ntasks); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -EINVAL); + return -1; + } + + if (!runmsg->taskhosts[0]->exdata || + runmsg->taskhosts[0]->exdata[0] == '\0') { + task_ctx->startinfo->exdata = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No exdata provided in runtasks request"); + } else { + task_ctx->startinfo->exdata = strdup((char*)runmsg->taskhosts[0]->exdata); + XCECD_LOG(XCE_LOG_DEBUG3, + "Received exdata: %s", + task_ctx->startinfo->exdata); + + if (!task_ctx->startinfo->exdata) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for exdata"); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + } + + if (!runmsg->taskhosts[0]->exechome || + runmsg->taskhosts[0]->exechome[0] == '\0') { + task_ctx->startinfo->exechome = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No exechome provided in runtasks request"); + } else { + task_ctx->startinfo->exechome = strdup((char*)runmsg->taskhosts[0]->exechome); + if (!task_ctx->startinfo->exechome) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for exechome"); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + } + + if (!runmsg->taskhosts[0]->execcwd || + runmsg->taskhosts[0]->execcwd[0] == '\0') { + task_ctx->startinfo->execcwd = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No execcwd provided in runtasks request"); + } else { + task_ctx->startinfo->execcwd = strdup((char*)runmsg->taskhosts[0]->execcwd); + if (!task_ctx->startinfo->execcwd) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for execcwd"); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + } + + /* currently we do not consider pre/post exec for tasks */ +#if 0 + if (!runmsg->taskhosts[0]->q_pre_exec || + runmsg->taskhosts[0]->q_pre_exec[0] == '\0') { + task_ctx->startinfo->q_pre_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No queue level pre-exec provided in runtasks request"); + } else { + task_ctx->startinfo->q_pre_exec = strdup((char*)runmsg->taskhosts[0]->q_pre_exec); + if (!task_ctx->startinfo->q_pre_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for queue level pre-exec command."); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + } + + if (!runmsg->taskhosts[0]->q_post_exec || + runmsg->taskhosts[0]->q_post_exec[0] == '\0') { + task_ctx->startinfo->q_post_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No queue level post-exec provided in runtasks request"); + } else { + task_ctx->startinfo->q_post_exec = strdup((char*)runmsg->taskhosts[0]->q_post_exec); + if (!task_ctx->startinfo->q_post_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for queue level post-exec command."); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + } + + if (!runmsg->taskhosts[0]->job_pre_exec || + runmsg->taskhosts[0]->job_pre_exec[0] == '\0') { + task_ctx->startinfo->job_pre_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No job level pre-exec provided in runtasks request"); + } else { + task_ctx->startinfo->job_pre_exec = strdup((char*)runmsg->taskhosts[0]->job_pre_exec); + if (!task_ctx->startinfo->job_pre_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for job level pre-exec command."); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + } + + if (!runmsg->taskhosts[0]->job_post_exec || + runmsg->taskhosts[0]->job_post_exec[0] == '\0') { + task_ctx->startinfo->job_post_exec = NULL; + XCECD_LOG(XCE_LOG_DEBUG3, + "No job level post-exec provided in runtasks request"); + } else { + task_ctx->startinfo->job_post_exec = strdup((char*)runmsg->taskhosts[0]->job_post_exec); + if (!task_ctx->startinfo->job_post_exec) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for job level post-exec command."); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } + } +#endif + +#ifndef DBUS_MANAGER + task_ctx->manager = (void*)cc_ctx->process_manager; +#endif + + task_ctx->func = xcecd_process_command; + task_ctx->xce_jobid = runmsg->jobid; + task_ctx->clientidx = runmsg->clientidx; + + if (submit_fork_task(cc_ctx->thread_pool, task_ctx) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to add task to thread pool"); + free_startinfo(task_ctx->startinfo); + FREEUP(task_ctx); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } else { + XCECD_LOG(XCE_LOG_DEBUG, + "Task added to thread pool queue"); + } + + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for task context"); + xcecd_task_fail(runmsg->jobid, + runmsg->clientidx, + -1, -ENOMEM); + return -1; + } +#endif // XRUN + return 0; +} + +/** + * @brief Handles the runtasks request from the controller. + * + * @param request Pointer to the xce_request_t structure containing the request data. + * @return int 0 on success, -1 on failure. + */ +static int handle_jobcontrol(xce_request_t* request) +{ +#ifndef XRUN + XCECD_LOG(XCE_LOG_DEBUG, + "Received job control request from controller"); + xce_jobctrl_t* d = request->body.jobctrlmsg; + + if (d->jobid <= 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Received job control request with invalid job ID"); + return -1; + } + + if (d->action == JobControl_u_siginfo) { + XCECD_LOG(XCE_LOG_DEBUG, + "Received job control request to send SIGINFO for job ID: %lu", + d->jobid); + + job_control_task_t* task_ctx = calloc(1, sizeof(job_control_task_t)); + if (!task_ctx) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for job control task context"); + return -1; + } + + task_ctx->jobid = d->jobid; + task_ctx->signal = d->u.siginfo->signo; + task_ctx->force = d->u.siginfo->force; + + xcecd_worker_t* worker = find_worker_by_jobid((xcecd_worker_t *)*cc_ctx->worker_list, + d->jobid); + + if (worker) { + if (worker->job_status == JOB_PENDING || worker->job_status == JOB_STARTING) { + XCECD_LOG(XCE_LOG_ERROR, + "Job is pending or starting, controller should not pass the signal to %lu.", + d->jobid); + + // really should not happen, but if it does, we handle it gracefully + xcecd_job_status_error(d->jobid); + FREEUP(task_ctx); + return 0; + } + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to find worker for job ID: %lu", + d->jobid); + FREEUP(task_ctx); + return -1; + } + +#if !defined(VIRTUAL_CLUSTER) + if (!ipc_master_send_job_signal(task_ctx->jobid, + task_ctx->signal, + task_ctx->force)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to submit job control task for job ID: %lu", + d->jobid); + FREEUP(task_ctx); + return -1; + } else { + XCECD_LOG(XCE_LOG_DEBUG, + "Job control task submitted for job ID: %lu", + d->jobid); + } + FREEUP(task_ctx); +#else + // For virtual cluster, we handle job control tasks differently + XCECD_LOG(XCE_LOG_DEBUG, + "Virtual cluster mode, handling job control task directly"); + FREEUP(task_ctx); + XCECD_LOG(XCE_LOG_DEBUG, + "Job control task handled directly for job ID: %lu", + d->jobid); + + worker->killed_signal = d->u.siginfo->signo; // Mark job as killed +#endif + } else if (d->action == JobControl_u_ckptoption) { +#if !defined(VIRTUAL_CLUSTER) + XCECD_LOG(XCE_LOG_DEBUG, + "Received job control request to set checkpoint options for job ID: %lu", + d->jobid); + + if (!ipc_master_send_job_ckpt(d->jobid, + d->u.ckptoption->kill, + d->u.ckptoption->method)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to send checkpoint options for job ID: %lu", + d->jobid); + return -1; + } else { + XCECD_LOG(XCE_LOG_DEBUG, + "Checkpoint options sent for job ID: %lu", + d->jobid); + } +#else + XCECD_LOG(XCE_LOG_WARN, + "Received job control request in virtual cluster or xrun mode, which is not supported"); +#endif + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Received unknown job control action: %d for job ID: %lu", + d->action, + d->jobid); + return -1; + } +#endif // XRUN + return 0; +} + +/** + * @brief Handles incoming requests from the controller. + * + * @param ctx Pointer to the I/O context structure. + * @return int 0 on success, -1 on failure. + */ +static int myreq_handler(xce_request_t* request) +{ + int ret = 0; + + XCECD_LOG(XCE_LOG_DEBUG, "Calling request handler..."); + + switch (request->opcode) { + case OPCode_startjob: + ret = handle_startjob(request); + break; + case OPCode_runtasks: + ret = handle_runtasks(request); + break; + case OPCode_jobcontrol: + ret = handle_jobcontrol(request); + break; + default: + break; + } + + return ret; +} + +/** + * @brief Sends a start job reply to the controller. + * + * @param job Pointer to the job structure. + * @return bool true on success, false on failure. + */ +bool xcecd_reply_startjob(xcecd_job_t* job) +{ + /* clang-format off */ + //xce_ctx_t* ctx = cc_ctx->xce_ctx; + unsigned int pids[] = {job->job_pid}; + job_pidinfo_t pidinfo = { + .jobid = job->job_id, + .npids = 1, + .pids = &pids[0], + .npgids = 0, + .pgids = NULL, + .exdata = NULL, + }; + + if (job->exdata[0] != '\0') { + pidinfo.exdata = strdup(job->exdata); + } + + xcecli_notify_jobstart(xcecli_ctx, &pidinfo); + + FREEUP(pidinfo.exdata); + + return true; +} + +/** + * @brief Starts the controller connector by joining the cluster and setting up + * the endpoint handler. + * + * @return bool true on success, false on failure. + */ +bool controller_connector_start(void) +{ + int rc; + int retry_limit = 0; + static int error_count = 0; + + init_client_config(&xce_client_cfg); + + do { + rc = xcecd_join_cluster(&host_attrs); + if (rc < 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "Failed to join cluster, retrying in %d seconds", + cc_ctx->join_interval); + error_count++; + if ((retry_limit > 0) && (error_count >= retry_limit)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to join cluster after %d retries", + retry_limit); + return false; + } + } + sleep(cc_ctx->join_interval); + } while (rc < 0 && cc_ctx->running); + + // api_ctx_t* api_ctx = (api_ctx_t*)xcecli_ctx; + // cc_ctx->xce_ctx = &(api_ctx->ctx); + + return true; +} + +int xcecd_notify_job_fail(uint64_t jobid, + int stage, + int code) +{ + job_failinfo_t failinfo = { + .jobid = jobid, + .stage = stage, + .code = code, + .detail = "no detail information" + }; + + XCECD_LOG(XCE_LOG_DEBUG, "Job failed and calling xcecli_notify_jobfail..."); + return xcecli_notify_jobfail(xcecli_ctx, &failinfo); +} + +/** + * @brief Marks a job as finished and sends the exit code to the controller. + * + * @param job Pointer to the job structure. + * @return int 0 on success, -1 on failure. + */ +int xcecd_job_finished(xcecd_job_t* job) +{ + if (job->exit_code != 0) { + return xcecd_notify_job_fail(job->job_id, + job->stage, + job->exit_code); + } + + xce_jobfinish_t myjob = { + .jobid = job->job_id, + .exit_code = 0 + }; + + XCECD_LOG(XCE_LOG_DEBUG, "calling xcecli_jobfinish..."); + return xcecli_notify_jobfinish(xcecli_ctx, &myjob); +} + +/** + * @brief Marks a job as stopped and sends a notification to the controller. + * + * @param jobid The ID of the job to be marked as stopped. + * @return int 0 on success, -1 on failure. + */ +int xcecd_job_stopped(uint64_t jobid) +{ + XCECD_LOG(XCE_LOG_DEBUG, + "Job stopped and calling xcecli_notify_jobstop..."); + + return xcecli_notify_jobstop(xcecli_ctx, jobid); +} + +/** + * @brief Marks a job as resumed and sends a notification to the controller. + * + * @param jobid The ID of the job to be marked as resumed. + * @return int 0 on success, -1 on failure. + */ +int xcecd_job_resumed(uint64_t jobid) +{ + XCECD_LOG(XCE_LOG_DEBUG, + "Job resumed and calling xcecli_notify_jobresume..."); + + return xcecli_notify_jobresume(xcecli_ctx, jobid); +} + +/** + * @brief Reply job start error information to controller + * for some cases, we need to requeue the job + * and let controller to retry + * @return int 0 on success, -1 on failure. + */ +int xcecd_job_start_error(uint64_t jobid) +{ + return xcecd_notify_job_fail(jobid, JOB_EXEC_STAGE_MAIN, -1); +} + +/** + * @brief Reply job status error information to controller + * for some cases, we need to requeue the job + * and let controller to retry + * @return int 0 on success, -1 on failure. + */ +int xcecd_job_status_error(uint64_t jobid) +{ + xce_jobfinish_t myjob = { + .jobid = jobid, + .exit_code = -1 + }; + + XCECD_LOG(XCE_LOG_DEBUG, "Job status error and calling xcecli_jobfinish..."); + return xcecli_jobfinish(xcecli_ctx, &myjob); +} + +/** + * @brief Reply job checkpoint result to controller + * @return int 0 on success, -1 on failure + */ +int xcecd_job_ckpted(uint64_t jobid, int status, const char* info) +{ + if (0 == status) { + XCECD_LOG(XCE_LOG_DEBUG, "Job checkpoint success and calling xcecli_notify_jobchkpt..."); + } else { + XCECD_LOG(XCE_LOG_ERROR, "Job checkpoint failed and calling xcecli_notify_jobchkpt..."); + } + + return xcecli_notify_jobchkpt(xcecli_ctx, jobid, (char*)info); +} + +/** + * @brief Starts a job on a remote host. + * + * @param hostname The name of the remote host. + * @param command The command to be executed on the remote host. + * @return int 0 on success, -1 on failure. + */ +int xcecd_job_remote_start(uint64_t jobid, + const char* hostname, + const char* command) +{ + int rc = 0; + + if (!cc_ctx || !cc_ctx->worker_list) { + XCECD_LOG(XCE_LOG_ERROR, + "Controller connector context is not initialized"); + return -1; + } + + if (!hostname || !command) { + XCECD_LOG(XCE_LOG_ERROR, + "Invalid parameters for remote job start"); + return -1; + } + + if (jobid <= 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Invalid job ID for remote job start"); + return -1; + } + + xcecd_worker_t* worker = find_worker_by_jobid((xcecd_worker_t *)*cc_ctx->worker_list, + jobid); + if (!worker) { + XCECD_LOG(XCE_LOG_ERROR, + "Worker not found for job ID %lu", jobid); + return -1; + } + + xce_taskhost_t host[] = { + {.exechome = worker->exechome, + .execcwd = worker->execcwd, + .cmdline = strdup(command), + .exdata = worker->job_exdata, + .execuser = worker->execuser, + .launch_type = worker->launch_type, + .host = strdup(hostname), + .ntasks = 1}, + {0} + }; + + xce_taskrun_t taskrun = { + .jobid = jobid, + .clientidx = -1, + .taskhosts = (xce_taskhost_t**)calloc(1, sizeof(xce_taskhost_t*)), + .nhosts = 1 + }; + + taskrun.taskhosts[0] = &host[0]; + + rc = xcecli_runtask(xcecli_ctx, &taskrun); + if (rc < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to start job %lu on remote host %s, rc: %d", + jobid, hostname, rc); + xcecd_task_fail(jobid, -1, -1, rc); + return rc; + } + + XCECD_LOG(XCE_LOG_DEBUG, + "Remote task of job %lu will run on %s", + jobid, hostname); + return 0; +} + +int xcecd_task_fail(uint64_t jobid, int clientidx, + int taskidx, int code) +{ + xce_task_failinfo_t failinfo = { + .jobid = jobid, + .host = xce_client_cfg.from, + .clientidx = clientidx, + .taskidx = taskidx, + .code = code, + .detail = NULL + }; + + return xcecli_notify_taskfail(xcecli_ctx, &failinfo); +} + +int xcecd_jobpost_done(uint64_t jobid) +{ + XCECD_LOG(XCE_LOG_DEBUG, + "Job post done and calling xcecli_notify_jobpostdone..."); + return xcecli_notify_jobpostdone(xcecli_ctx, jobid); +} \ No newline at end of file diff --git a/src/worker/src/hardware_monitor.c b/src/worker/src/hardware_monitor.c new file mode 100644 index 00000000..b8c75912 --- /dev/null +++ b/src/worker/src/hardware_monitor.c @@ -0,0 +1,162 @@ +#include "hardware_monitor.h" +#include "xcelog.h" +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#endif + +struct hardware_monitor { + char* acct_file; + int last_cpu_cores; + double total_memory; + double free_memory; + time_t last_update_time; +}; + +static void write_to_acct_file(hardware_monitor_t* monitor, int cpu_cores) { + XCECD_LOG(XCE_LOG_INFO, "Writing to acct file..."); + FILE* file = fopen(monitor->acct_file, "a"); + if (!file) { + return; + } + + time_t now; + time(&now); + struct tm* timeinfo = localtime(&now); + char timestamp[64]; + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", timeinfo); + + fprintf(file, "%s\t%d\n", timestamp, cpu_cores); + fclose(file); +} + +hardware_monitor_t* hardware_monitor_create(const char* acct_file) { + hardware_monitor_t* monitor = calloc(1, sizeof(hardware_monitor_t)); + if (!monitor) { + return NULL; + } + + monitor->acct_file = strdup(acct_file); + monitor->last_cpu_cores = -1; + monitor->last_update_time = 0; + monitor->total_memory = 0; + monitor->free_memory = 0; + + XCECD_LOG(XCE_LOG_INFO, "Hardware monitor created."); + return monitor; +} + +void hardware_monitor_destroy(hardware_monitor_t* monitor) { + if (monitor) { + free(monitor->acct_file); + free(monitor); + } +} + +bool hardware_monitor_update(hardware_monitor_t* monitor) { + if (!monitor) { + return false; + } + + hwloc_topology_t topology; + if (hwloc_topology_init(&topology) < 0 || + hwloc_topology_load(topology) < 0) { + return false; + } + + int current_cores = hwloc_get_nbobjs_by_type(topology, HWLOC_OBJ_CORE); + + // 如果CPU核心数发生变化,记录到文件 + if (current_cores != monitor->last_cpu_cores) { + write_to_acct_file(monitor, current_cores); + monitor->last_cpu_cores = current_cores; + } + + int64_t total_memory, free_memory; + if (hardware_monitor_get_memory_info(&total_memory, &free_memory)) { + monitor->total_memory = total_memory; + monitor->free_memory = free_memory; + } + + hwloc_topology_destroy(topology); + monitor->last_update_time = time(NULL); + return true; +} + +int hardware_monitor_get_cpu_cores(hardware_monitor_t* monitor) { + if (!monitor) { + return -1; + } + return monitor->last_cpu_cores; +} + +#ifdef _WIN32 +bool hardware_monitor_get_memory_info(int64_t* total_memory, int64_t* free_memory) { + if (!total_memory || !free_memory) { + return false; + } + + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + if (!GlobalMemoryStatusEx(&status)) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to get memory status: %d", GetLastError()); + return false; + } + + *total_memory = status.ullTotalPhys; + *free_memory = status.ullAvailPhys; + + return true; +} +#else +bool hardware_monitor_get_memory_info(int64_t* total_memory, int64_t* free_memory) { + if (!total_memory || !free_memory) { + return false; + } + + hwloc_topology_t topology; + if (hwloc_topology_init(&topology) < 0 || + hwloc_topology_load(topology) < 0) { + return false; + } + + hwloc_obj_t root = hwloc_get_root_obj(topology); + *total_memory = root->total_memory; + + struct sysinfo info; + if (sysinfo(&info) != 0) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to get sysinfo: %s", strerror(errno)); + return false; + } + *free_memory = (size_t)info.freeram * info.mem_unit; + + hwloc_topology_destroy(topology); + return true; +} +#endif + +double hardware_monitor_get_total_memory(hardware_monitor_t* monitor) { + if (!monitor) { + return 0; + } + return monitor->total_memory; +} + +double hardware_monitor_get_free_memory(hardware_monitor_t* monitor) { + if (!monitor) { + return 0; + } + return monitor->free_memory; +} \ No newline at end of file diff --git a/src/worker/src/inter_job_utils.c b/src/worker/src/inter_job_utils.c new file mode 100644 index 00000000..48b87d6c --- /dev/null +++ b/src/worker/src/inter_job_utils.c @@ -0,0 +1,327 @@ +#include "inter_job_utils.h" +#include "xcecd_utils.h" +#include "xcelog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * @brief Check if a port is available for binding. + * @param port_number The port number to check. + * @return 0 if the port is available, 1 if it is in use, + * -1 if an error occurred. + */ +int check_port_availability(int port_number) { + int sockfd; + struct sockaddr_in server_addr; + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Error creating socket"); + return -1; + } + + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port_number); + server_addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == 0) { + // successfully bound to the port + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, "Port %d is available", port_number); + close(sockfd); + return 0; + } else { + if (errno == EADDRINUSE) { + // Error: Address already in use + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Port %d is already in use", port_number); + close(sockfd); + return 1; + } else { + // Other error occurred + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Error binding socket: %s", strerror(errno)); + close(sockfd); + return -1; + } + } +} + +/** + * @brief Find an available port within a specified range. + * @param start_port The starting port number of the range. + * @param end_port The ending port number of the range. + * @return An available port number if found, -1 if no ports are available, + * or -2 if an error occurred (e.g., invalid range). + */ +int find_available_port(int start_port, int end_port) { + if (start_port < 1 || end_port > 65535 || start_port > end_port) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid port range: %d - %d\n", + start_port, end_port); + return -2; + } + + for (int port = start_port; port <= end_port; ++port) { + int status = check_port_availability(port); + if (status == 0) { + return port; + } else if (status == 1) { + continue; // Port is in use, try next one + } else if (status == -2) { + // system error occurred + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "System error occurred while checking port %d. Aborting scan.\n", port); + return -2; + } + } + return -1; +} + +/** + * @brief Generate a command string based on the job command and port. + * @param job_cmd The command to execute. + * @param port info to use for the job. + * @param interactive_mode The mode of interaction (TTY, DISPLAY, XJOB, etc.). + * @return A dynamically allocated command string, or NULL on failure. + */ +char* generate_cmd(char* job_cmd, port_info_t port, int interactive_mode) { + if (!job_cmd) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Job command is NULL"); + return NULL; + } + + if (interactive_mode == NORMAL) { + return strdup(job_cmd); + } + + char* cmd = (char*)calloc(1, CMD_MAX_LENGTH); + if (!cmd) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Memory allocation failed for command string"); + return NULL; + } + + char launcher[MAXFILENAMELEN] = {0}; + char exe_path[MAXFILENAMELEN - 5] = {0}; + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len > 0) { + exe_path[len] = '\0'; + char* last_slash = strrchr(exe_path, '/'); + if (last_slash) { + *last_slash = '\0'; + snprintf(launcher, sizeof(launcher), "%s/ttyd", exe_path); + } else { + snprintf(launcher, sizeof(launcher), "/opt/xce/bin/ttyd"); + } + } else { + snprintf(launcher, sizeof(launcher), "/opt/xce/bin/ttyd"); + } + + if (access(launcher, X_OK) != 0) { + if (interactive_mode == TTY || + interactive_mode == DISPLAY) { + // For interactive modes, if ttyd is not found, return error + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "ttyd not found or not executable at %s, cannot run interactive job", + launcher); + free(cmd); + return NULL; + } + } + + if (interactive_mode == TTY) { + snprintf(cmd, CMD_MAX_LENGTH, + "%s -WQ -p %d %s", + launcher, port.tty_port, job_cmd); + } else if (interactive_mode == DISPLAY) { + snprintf(cmd, CMD_MAX_LENGTH, + "%s -p %d -Q %s", + launcher, port.tty_port, job_cmd); + } else if (interactive_mode == XJOB) { + int dport = port.xjob_port - 5900; // Adjust port for VNC + if (dport < 1 || dport > 99) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid XJOB port %d, must be in range 5901-5999", + port.xjob_port); + free(cmd); + return NULL; + } + + snprintf(cmd, CMD_MAX_LENGTH, "%s", job_cmd); + } else { + // Default case for normal command execution + free(cmd); + return strdup(job_cmd); + } + + return cmd; +} + +/** + * @brief Get the local IP address of the machine. + * @return A dynamically allocated string containing the IP address, + * or NULL on failure. Caller should free the returned string. + */ +static char* get_local_ip() { + struct ifaddrs *ifaddrs_ptr = NULL; + struct ifaddrs *ifa = NULL; + char* ip_str = NULL; + + if (getifaddrs(&ifaddrs_ptr) == -1) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to get network interfaces"); + return NULL; + } + + for (ifa = ifaddrs_ptr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) continue; + + if (ifa->ifa_addr->sa_family == AF_INET && + strcmp(ifa->ifa_name, "lo") != 0 && + strcmp(ifa->ifa_name, "lo0") != 0) { + + struct sockaddr_in* addr = (struct sockaddr_in*)ifa->ifa_addr; + char* temp_ip = inet_ntoa(addr->sin_addr); + + if (strncmp(temp_ip, "169.254.", 8) != 0) { + ip_str = strdup(temp_ip); + break; + } + } + } + + freeifaddrs(ifaddrs_ptr); + + if (!ip_str) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Could not find valid IP address."); + } + + return ip_str; +} + +/** + * @brief Generate a URL based on the port and interaction mode. + * The url will be in the format: + * "http://:" + * @param port The port information to use. + * @param interactive_mode The mode of interaction (TTY, DISPLAY, XJOB, etc.). + * @return A dynamically allocated URL string, or NULL on failure. + */ +char* generate_url(port_info_t port, int interactive_mode) { + if (interactive_mode < 0 || interactive_mode > 3) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid interactive mode specified"); + return NULL; // Invalid mode + } + if (interactive_mode == NORMAL) { + return NULL; // No URL for normal command execution + } + + char* url = (char*)calloc(1, URL_MAX_LENGTH); + if (!url) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Memory allocation failed for URL string"); + return NULL; + } + + char* local_ip = get_local_ip(); + if (!local_ip) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to get local IP address"); + free(url); + return NULL; + } + + if (interactive_mode == TTY || interactive_mode == DISPLAY) { + snprintf(url, CMD_MAX_LENGTH, "http://%s:%d", + local_ip, port.tty_port); + } else if (interactive_mode == XJOB) { + snprintf(url, CMD_MAX_LENGTH, "%s::%d", + local_ip, port.xjob_port); + } else { + free(url); + free(local_ip); + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid interactive mode specified"); + return NULL; // No URL for normal command execution + } + + free(local_ip); + return url; +} + +/** + * @brief Check if a port is available for the given port information. + * This function will call find_available_port to check + * if the port is available for the specified interaction mode. + * @param port The port information to check. + * @param interactive_mode The mode of interaction (TTY, DISPLAY, XJOB, etc.). + * @return port number if available, -1 if not available, + * or -2 if an error occurred. + */ +int check_port(port_info_t port, int interactive_mode) { + if (interactive_mode < 0 || interactive_mode > 3) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid interactive mode specified"); + return -2; // Invalid mode + } + + if (interactive_mode == NORMAL) { + // For normal mode, we don't need to check a specific port + return 0; // No port needed for normal execution + } + + if (port.tty_port == TTY_END_PORT) { + port.tty_port = 9000; // Default TTY port + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "TTY port maxed, using default: %d", port.tty_port); + } + + if (port.xjob_port == XJOB_END_PORT) { + port.xjob_port = 5901; // Default XJOB port + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "XJOB port maxed, using default: %d", port.xjob_port); + } + + if (interactive_mode == TTY) { + return find_available_port(port.tty_port, TTY_END_PORT); + } else if (interactive_mode == DISPLAY) { + return find_available_port(port.tty_port, TTY_END_PORT); + } else if (interactive_mode == XJOB) { + return find_available_port(port.xjob_port, XJOB_END_PORT); + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid interactive mode specified"); + return -2; // Invalid mode + } +} + +/** + * @brief This function accepts a xjob port number and exports it + * as DISPLAY environment variable. + * @param port The port number to export as DISPLAY. + * @return 0 on success, -1 on failure. + */ +int export_display_env(int port) { + char display_env[32]; + snprintf(display_env, sizeof(display_env), + ":%d", port - 5900); + + if (setenv("DISPLAY", display_env, 1) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to set DISPLAY environment variable"); + return -1; + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Exported DISPLAY environment variable: %s", display_env); + return 0; +} diff --git a/src/worker/src/job_information.c b/src/worker/src/job_information.c new file mode 100644 index 00000000..aa708672 --- /dev/null +++ b/src/worker/src/job_information.c @@ -0,0 +1,105 @@ +#include "job_information.h" +#include "xcecd_utils.h" +#include "xcelog.h" +#include +#include +#include + +// 初始化job_setup结构体 +void init_job_setup(struct job_setup* setup, const char* data) { + if (!setup || !data) { + return; + } + + // 假设data是一个JSON字符串,解析并初始化job_setup结构体 + // 这里使用简单的字符串解析,实际应用中建议使用JSON解析库 + sscanf(data, "{\"job_id\":%ld,\"job_status\":%d,\"cpu_time\":%f,\"w_status\":%d}", + &setup->job_id, &setup->job_status, &setup->cpu_time, &setup->w_status); + + // 初始化其他字段 + setup->reason = 0; + setup->job_pid = 0; + setup->job_pgid = 0; + setup->exec_gid = 0; + setup->exec_uid = 0; + strcpy(setup->exec_user_name, "user01"); + strcpy(setup->exec_home, "/home/user01"); + strcpy(setup->exec_cwd, "/home/user01"); + setup->exec_job_flag = 0; +} + +// 初始化job_card结构体 +void init_job_card(struct job_card* card, const char* data) { + if (!card || !data) { + return; + } + + // 假设data是一个JSON字符串,解析并初始化job_card结构体 + // 这里使用简单的字符串解析,实际应用中建议使用JSON解析库 + sscanf(data, "{\"exec_user_name\":\"%[^\"]\",\"exec_gid\":%d,\"cpu_time\":%f}", + card->exec_user_name, &card->exec_gid, &card->cpu_time); + + // 初始化其他字段 + card->exec_job_flag = 0; + card->not_reported = 0; + card->wind_edge = 0; + card->active = 0; + card->time_expire = 0; + card->missing = 0; + card->controller_restarted = 0; + card->wind_warn_time = 0; + card->runtime = 0; + card->w_status = 0; + card->need_report_rusage = 0; + card->cleanup_pid = 0; + card->collected_child = 0; + card->stdin_file = NULL; + card->last_controller_time = 0; + card->delievered_message_id = 0; + card->client = NULL; + card->reg_op_flag = 0; + card->job_sup_status = 0; + card->act_flags = 0; + card->act_status = 0; + card->act_reasons = 0; + card->act_sub_reasons = 0; + card->exit_pid = 0; + card->job_done = 0; + card->last_check = 0; + card->act_cmd = NULL; + card->exit_file = NULL; + card->cluster_name = NULL; + card->server_socket = 0; + card->cross_platforms = 0; + card->spooled_exec = NULL; + card->post_job_started = 0; + card->user_job_succeed = 0; +} + +void cleanup_job_card(struct job_card* card) { + if (!card) { + return; + } + + FREEUP(card->stdin_file); + FREEUP(card->act_cmd); + FREEUP(card->exit_file); + FREEUP(card->cluster_name); + FREEUP(card->spooled_exec); +} + +struct client_node* init_client_node(long id) { + struct client_node* node = (struct client_node*)calloc(1, sizeof(struct client_node)); + if (!node) { + return NULL; + } + + node->job_id = id; + node->job_type = 0; + node->jp = NULL; + + node->prev = NULL; + node->next = NULL; + + return node; +} diff --git a/src/worker/src/main.c b/src/worker/src/main.c new file mode 100644 index 00000000..cadbd875 --- /dev/null +++ b/src/worker/src/main.c @@ -0,0 +1,542 @@ +#include "xcecd.h" +#include "sqlite3.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#endif + +/* TEMP SOLUTION: use config later */ +#define LOG_SIZE (1024 * 1024 * 100) // 100 MB +#define LOG_LEVEL 2 // DEBUG level + +extern xcecd_context_t xcecd_ctx; +volatile sig_atomic_t child_exited = 0; + +/* job status using for communication with controller, should be a list? */ +static xcecd_job_t job = { + .job_id = 0, + .job_pid = 0, + .job_status = JOB_STARTING, + .exit_code = 0, + .stage = JOB_EXEC_STAGE_MAIN, + .interactive_port = 0, + .exdata = {0} +}; +static StaticArray pids; + + +static void signal_handler(int signo) { + if (signo == SIGINT || signo == SIGTERM) { + XCECD_LOG(XCE_LOG_WARN, + "Received signal %d, shutting down...", + signo); + exit_flag = 1; + + /* mark to normal restart */ + xcecd_prepare_restart(); +#if 0 + /* kill all jobs and update DB */ + dbus_stop_all_workers(&xcecd_ctx.worker_list, + (sqlite3*)xcecd_ctx.db); +#endif + xcecd_ctx.running = false; + xcecd_stop(); + } +} + +static void alarm_handler(int sig) { + if (sig == SIGALRM) { + exit_flag = 1; + XCECD_LOG(XCE_LOG_ERROR, + "Program running timeout, force exit"); + + kill(0, SIGKILL); + _exit(1); + } +} + +static void sigchld_handler(int signo) { + (void)signo; + int status; + pid_t pid; + int i = 0; + + child_exited = 1; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + if (WIFEXITED(status) || WIFSIGNALED(status)) { + if (i++ < MAX_ARRAY_SIZE) { + /* just recording the exited pids and handle them in main loop */ + if (!push_static(&pids, &pid)) { + XCECD_LOG(XCE_LOG_ERROR, + "Fatal error: failed to store exit pid %d", + pid); + } + } else { + /* reach array max limit and handle rest pid later */ + XCECD_LOG(XCE_LOG_DEBUG, + "Too many workers exited, pid %d. Please reduce your slots on this host.", + pid); + break; + } + } + } +} + +static void handle_exited_children(process_manager_t* manager, + StaticArray pids) +{ + int pid; + + for (int i = 0; i < pids.count ; i++) { + if (!get_element(&pids, i, &pid)) { + XCECD_LOG(XCE_LOG_ERROR, + "handle_exited_children: failed to get pid"); + } + process_manager_remove_process(manager, pid); + } + + clear_static(&pids); +} + +static void job_status_cleanup(xcecd_job_t* job) { + job->job_id = 0; + job->job_pid = 0; + job->job_status = JOB_UNKNOWN; + job->exit_code = 0; + job->stage = JOB_EXEC_STAGE_MAIN; + job->interactive_port = 0; + memset(job->exdata, 0, sizeof(job->exdata)); +} + +static void handle_message_from_queue(void) { + thread_pool_t* pool = xcecd_ctx.thread_pool; + fork_task_t task; + + if (queue_pop(&pool->fork_queue, &task) == 0) { + /* handle fork task */ + task.func(&task); + } +} + +static void master_disconnect_callback(uint64_t job_id) { + XCECD_LOG(XCE_LOG_DEBUG, + "Worker with client ID %lu disconnected", + job_id); +} + +static void master_job_status_callback(job_status_t* job_status) +{ + int ret = XCECD_OK; + + XCECD_LOG(XCE_LOG_INFO, + "Job %lu status: %d, exdata: %s, %s", + job_status->job_id, + job_status->status_code, + job_status->start_exdata[0] ? job_status->start_exdata : "no exdata", + job_status->status_message); + + job.job_status = job_status->status_code; + job.job_pid = job_status->job_pid; + job.exit_code = job_status->exit_code; + strncpy(job.exdata, + job_status->start_exdata, + sizeof(job.exdata) - 1); + job.exdata[sizeof(job.exdata) - 1] = '\0'; + + if ((job_status->stage == JOB_EXEC_STAGE_POST_EXEC) || + (job_status->stage == JOB_EXEC_STAGE_Q_POST_EXEC)) { + /* Post-exec status update */ + + if (job_status->post_done) { + xcecd_jobpost_done(job_status->job_id); + } else { + xcecd_notify_job_fail(job_status->job_id, + job_status->stage, + job_status->exit_code); + } + + job_status_cleanup(&job); + return; + } + + /* update worker status and send job status to controller */ + ret = update_worker_status(xcecd_ctx.worker_list, + job_status->worker_pid, + job_status->worker_status, + &job, + (sqlite3*)xcecd_ctx.db); + + if (ret == XCECD_DB_ERROR) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + dbus_stop_all_workers(&xcecd_ctx.worker_list, + NULL); + xcecd_db_is_available(xcecd_ctx.db, + xcecd_ctx.db_path); + } + + job_status_cleanup(&job); +} + +static void master_heartbeat_callback(uv_stream_t* client) { + XCECD_LOG(XCE_LOG_DEBUG, + "Received heartbeat from worker, sending response."); + uv_write_t* req = (uv_write_t*)malloc(sizeof(uv_write_t)); + if (req) { + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_HEARTBEAT; + uv_buf_t send_buf = uv_buf_init((char*)&msg, sizeof(ipc_message_t)); + uv_write(req, client, &send_buf, 1, on_write_complete); + } +} + +static void master_ckpt_result_callback(uint64_t job_id, + int status, + const char* restore_cmd) +{ + XCECD_LOG(XCE_LOG_DEBUG, + "Received checkpoint result for job %lu: status %d, restore_cmd: %s", + job_id, status, restore_cmd ? restore_cmd : "NULL"); + + if (job_id <= 0) { + XCECD_LOG(XCE_LOG_ERROR, "Job ID is not correct."); + xcecd_job_ckpted(job_id, 1, NULL); + return; + } + + xcecd_job_ckpted(job_id, status, restore_cmd); +} + +int main(int argc, char* argv[]) { + int opt; + config_result_t *config_result = NULL; + int ret = XCECD_OK; + int config_flag = 0; + char* custom_config_path = NULL; + __attribute__((unused)) char worker_log[1024] = {0}; + + signal(SIGPIPE, SIG_IGN); + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sa.sa_flags = SA_RESTART; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + struct sigaction sa_alrm; + memset(&sa_alrm, 0, sizeof(sa_alrm)); + sa_alrm.sa_handler = alarm_handler; + sigaction(SIGALRM, &sa_alrm, NULL); + + struct sigaction sa_chld; + memset(&sa_chld, 0, sizeof(sa_chld)); + sa_chld.sa_handler = sigchld_handler; + sa_chld.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT; + sigaction(SIGCHLD, &sa_chld, NULL); + + xcecd_config_t config = { + .thread_pool_size = 1, + .job_status_update_interval = 1, + .port = 0, + .acct_file = "/tmp/xcecd.acct", + .log_file = "/tmp/xcecd.log", + .db_path = "/var/run/xcecd.db", + .work_dir = "/tmp", + .domain_sock_file = "/var/run/.xcecd.sock", + .virutal_name = "localhost01", + .remote_host = "localhost", + .remote_port = 6789, + .join_interval = 5, + .update_interval = 5, + .resource_update_interval = 1, + .debug_level = XCE_LOG_INFO + }; + + char logfile[1024] = {0}; + char *logdir = NULL; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) { + custom_config_path = argv[i + 1]; + config_flag = 1; + break; + } + } + + if (config_flag == 0) { + custom_config_path = strdup(DEFAULT_CONFIG_PATH); + } + + if (access(custom_config_path, F_OK) == 0) { + + /* load config from custom file */ + config_result = config_load(custom_config_path); + if (!config_result) { + fprintf(stderr, "Failed to load config from %s\n", custom_config_path); + return -1; + } + + if (config_result->error_code == CONFIG_SUCCESS) { + if (config_result->config->controller_port > 0) { + config.remote_port = config_result->config->controller_port; + } + + if (config_result->config->log_dir) { + logdir = strdup(config_result->config->log_dir); + } + + if (config_result->config->log_level) { + config.debug_level = level_to_int(config_result->config->log_level); + } + + if (config_result->config->management_list_count > 0) { + config.remote_host = config_result->config->management_list[0].hostname; + } + } + } else { + fprintf(stderr, "Configuration file %s not found.\n", custom_config_path); + return -1; + } + + while ((opt = getopt(argc, argv, "c:p:m:n:l:h")) != -1) { + switch (opt) { + case 'c': + break; // Custom config file already handled above + case 'p': + if (config_flag) { + fprintf(stderr, "Custom config file already specified, ignoring -p option.\n"); + break; + } + config.remote_port = atoi(optarg); + break; + case 'm': + if (config_flag) { + fprintf(stderr, "Custom config file already specified, ignoring -m option.\n"); + break; + } + config.remote_host = optarg; + break; + case 'n': + config.virutal_name = optarg; + break; + case 'l': + if (config_flag) { + fprintf(stderr, "Custom config file already specified, ignoring -l option.\n"); + break; + } + if (optarg) { + if (logdir) { + FREEUP(logdir); + } + + logdir = strdup(optarg); + } + break; + case 'h': + default: +#ifdef VIRTUAL_CLUSTER + printf("Usage: %s -n virtual_name [-p remote_port] [-m remote_host] [-l log_dir] [-h help]\n", argv[0]); +#else + printf("Usage: %s [-p remote_port] [-m remote_host] [-l log_dir ] [-h help]\n", argv[0]); +#endif + return -1; + } + } + + struct stat st = {0}; + + if (stat(logdir, &st) == -1) { + if (mkdir(logdir, 0755) != 0) { + fprintf(stderr, "Failed to create log directory %s: %s\n", + logdir, strerror(errno)); + FREEUP(logdir); + return -1; + } + } + chmod(logdir, 0777); + +#ifdef VIRTUAL_CLUSTER + snprintf(logfile, sizeof(logfile), "%s/xcecd-%s.log", + logdir ? logdir : "/tmp", config.virutal_name); +#else + snprintf(logfile, sizeof(logfile), "%s/xcecd.log", + logdir ? logdir : "/tmp"); + + /* we need create worker log here and set it to 0666 */ + snprintf(worker_log, sizeof(worker_log), "%s/xcecd_worker.log", + logdir ? logdir : "/tmp"); +#endif + + if (logdir) { + FREEUP(logdir); + } + config.log_file = logfile; + + /* array to store exit worker pids */ + init_static_array(&pids, sizeof(int)); + +#ifndef _WIN32 +#if 0 + pid_t pid = fork(); + if (pid < 0) { + fprintf(stderr, "First fork failed\n"); + return 1; + } + if (pid > 0) { + exit(0); + } + + if (setsid() < 0) { + fprintf(stderr, "setsid failed\n"); + return 1; + } +#endif + char current_dir[1024]; + getcwd(current_dir, sizeof(current_dir)); + + config.work_dir = current_dir; + + chdir("/"); + + setbuf(stdout, NULL); + + //int fd = open(config.log_file, O_WRONLY | O_CREAT | O_APPEND, 0644); + int fd = open("/home/user01/xcecd.log", O_WRONLY | O_CREAT | O_APPEND, 0644); + if (fd != -1) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + +#endif + + if (xce_init_xcecd_log(config.log_file, LOG_SIZE, config.debug_level) != 0) { + /* FIXME: write error message to file. */ + fprintf(stderr, "Failed to initialize log module."); + return 1; + } + +#ifndef VIRTUAL_CLUSTER + /* create worker log file */ + int worker_log_fd = open(worker_log, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (worker_log_fd != -1) { + XCECD_LOG(XCE_LOG_INFO, + "Worker log file created: %s", worker_log); + } + + /* close it and wait real worker to use */ + close(worker_log_fd); + chmod(worker_log, 0666); +#endif + + if (xcecd_config_init(&config) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to initialize daemon configuration."); + return 1; + } + + + if (xcecd_init(&config) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to initialize XCECD daemon."); + return 1; + } + + if (xcecd_start() != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to start XCECD daemon."); + xcecd_cleanup(); + return 1; + } + +#ifndef VIRTUAL_CLUSTER + if (!ipc_master_init(config.domain_sock_file)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to initialize XCECD IPC."); + xcecd_cleanup(); + return 1; + } + + chmod(config.domain_sock_file, 0666); +#endif + + ipc_master_set_status_callback(master_job_status_callback); + ipc_master_set_heartbeat_callback(master_heartbeat_callback); + ipc_master_set_disconnect_callback(master_disconnect_callback); + ipc_master_set_ckpt_callback(master_ckpt_result_callback); + + XCECD_LOG(XCE_LOG_INFO, "XCECD started successfully."); + + /* waiting for signals and housekeeping */ + while (xcecd_ctx.running) { + + /* cleanup must following with worker update */ + cleanup_worker_list(&xcecd_ctx.worker_list, + (sqlite3*)xcecd_ctx.db); + + handle_message_from_queue(); + + usleep(10 * 1000); + + /* job should be running now */ + + ipc_master_run(UV_RUN_NOWAIT); + +#ifndef DBUS_MANAGER + worker_process_update(xcecd_ctx.process_manager, + xcecd_ctx.worker_list, + &job, + (sqlite3*)xcecd_ctx.db); +#else + ret = dbus_workers_update(&xcecd_ctx.worker_list, + &job, + (sqlite3*)xcecd_ctx.db); + + if (ret == XCECD_DB_ERROR) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + dbus_stop_all_workers(&xcecd_ctx.worker_list, + NULL); + xcecd_db_is_available(xcecd_ctx.db, + xcecd_ctx.db_path); + } +#endif + ipc_master_run(UV_RUN_NOWAIT); + + /* handling child process exit in main process to prevent deadlock */ + if (child_exited) { + handle_exited_children(xcecd_ctx.process_manager, + pids); + child_exited = 0; + } + + } + + xcecd_cleanup(); + + unlink(config.domain_sock_file); + + XCECD_LOG(XCE_LOG_INFO, "XCECD is down."); + + alarm(5); + + xce_log_fini(); + + exit(0); +} diff --git a/src/worker/src/node_information.c b/src/worker/src/node_information.c new file mode 100644 index 00000000..96ec0add --- /dev/null +++ b/src/worker/src/node_information.c @@ -0,0 +1,88 @@ +#include "node_information.h" +#include "xcelog.h" +#include +#include +#include +#include +#include +#include + + + +// 获取当前CPU使用情况 +int get_cpu_usage(int total_cores, int used_cores) { + if (used_cores == 0) { + return total_cores; + } + + if (used_cores >= total_cores) { + return 0; + } + + if (used_cores > 0 && used_cores < total_cores) { + return total_cores - used_cores; + } + + // 其它状况 + return 0; +} + +host_node_t* host_node_create(void) { + host_node_t* node_ptr = (host_node_t*)calloc(1, sizeof(host_node_t)); + if (node_ptr == NULL) { + return NULL; + } + + node_ptr->node_name = NULL; + node_ptr->node_number = 0; + node_ptr->node_model_number = 0; + node_ptr->node_type_number = 0; + node_ptr->node_ip = NULL; + + node_ptr->node_status.node_id = 0; + node_ptr->node_status.max_cpu_cores = 0; + node_ptr->node_status.max_memory = 0; + node_ptr->node_status.num_disk = 0; + node_ptr->node_status.max_swap = 0; + node_ptr->node_status.max_network = 0; + node_ptr->node_status.port_number = 0; + + /* TODO: initialize load_index and user_load_index */ + + node_ptr->use = -1; + node_ptr->expire_time = -1; + + return node_ptr; +} + +int host_node_update(host_node_t* node_ptr, host_node_t* new_node) { + if (node_ptr == NULL || new_node == NULL) { + return -1; + } + + /* TODO: add implementation here */ + return 0; +} + +void free_host_node(host_node_t* node_ptr) { + if (node_ptr == NULL) { + return; + } + + FREEUP(node_ptr->node_name); + FREEUP(node_ptr->node_ip); + FREEUP(node_ptr->windows); + + for (int i = 0; i < 8; i++) { + FREEUP(node_ptr->week[i]); + } + + FREEUP(node_ptr->load_index); + FREEUP(node_ptr->user_load_index); + FREEUP(node_ptr->status); + FREEUP(node_ptr->busy_threshold); + FREEUP(node_ptr->resource_bitmap); + FREEUP(node_ptr->dynamic_resource_bitmap); + + FREEUP(node_ptr); +} \ No newline at end of file diff --git a/src/worker/src/process_manager.c b/src/worker/src/process_manager.c new file mode 100644 index 00000000..e7efa3a6 --- /dev/null +++ b/src/worker/src/process_manager.c @@ -0,0 +1,586 @@ +#include "process_manager.h" +#include "xcecd_utils.h" +#include "xcelog.h" +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#define MAX_PROCESSES 1024 +#define BUFFER_SIZE 4096 + +void process_info_init(process_info_t* proc) { + proc->handle = 0; + proc->command = NULL; + proc->status = 0; + proc->exit_code = 0; + proc->output_fd = -1; + proc->log_file = NULL; +#ifdef _WIN32 + proc->pipe_read = NULL; + proc->monitor_thread = NULL; + proc->monitor_active = false; +#endif + XCECD_LOG(XCE_LOG_DEBUG, "Process info initialized"); +} + +#ifdef _WIN32 +// 监控线程函数,用于读取进程输出并写入日志文件 +static DWORD WINAPI output_monitor_thread(LPVOID arg) { + process_info_t* proc = (process_info_t*)arg; + char buffer[BUFFER_SIZE]; + DWORD bytes_read; + + while (proc->monitor_active) { + if (ReadFile(proc->pipe_read, buffer, BUFFER_SIZE - 1, &bytes_read, NULL) && bytes_read > 0) { + buffer[bytes_read] = '\0'; + _write(proc->output_fd, buffer, bytes_read); + } else { + if (GetLastError() != ERROR_NO_DATA) { + break; + } + Sleep(100); // 避免过度消耗 CPU + } + } + + return 0; +} +#endif + +process_manager_t* process_manager_create(void) { + process_manager_t* manager = calloc(1, sizeof(process_manager_t)); + if (!manager) { + return NULL; + } + + manager->processes = NULL; + manager->process_count = 0; + manager->running = true; + manager->initialized = false; + +#ifdef _WIN32 + InitializeCriticalSection(&manager->lock); +#else + pthread_mutex_init(&manager->lock, NULL); +#endif + XCECD_LOG(XCE_LOG_DEBUG, "Process manager created"); + + return manager; +} + + +int process_manager_start_process(process_manager_t* manager, const char* command) { + + if (!manager || !command) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Invalid process manager or command"); + return -1; + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, "Starting process: %s", command); + +#ifdef _WIN32 + EnterCriticalSection(&manager->lock); +#else + pthread_mutex_lock(&manager->lock); +#endif + + // 创建新的进程信息结构 + process_info_t* new_processes = realloc(manager->processes, + (manager->process_count + 1) * sizeof(process_info_t)); + if (!new_processes) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to allocate memory for process info"); +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + return -1; + } + manager->processes = new_processes; + + process_info_t* proc = &manager->processes[manager->process_count]; + process_info_init(proc); + proc->command = strdup(command); + + char *jobid_str = getenv("XCE_JOBID"); + uint64_t jobid = 0; + if (jobid_str != NULL) { + jobid = atoll(jobid_str); + } + + // 创建日志文件 + char log_path[256]; + time_t now; + time(&now); + struct tm* timeinfo = localtime(&now); + if (jobid != 0) { + snprintf(log_path, sizeof(log_path), "/tmp/job_%lu_%lu.out", (unsigned long)now, jobid); + XCECD_WORKER_LOG(XCE_LOG_INFO, "Job output file path is: %s", log_path); + } else { + snprintf(log_path, sizeof(log_path), "/tmp/job_%d_%02d%02d_%02d%02d%02d.out", + (int)now, + timeinfo->tm_mon + 1, + timeinfo->tm_mday, + timeinfo->tm_hour, + timeinfo->tm_min, + timeinfo->tm_sec); + } + proc->log_file = strdup(log_path); + + // 打开日志文件 + proc->output_fd = open(proc->log_file, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (proc->output_fd == -1) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to open log file: %s", strerror(errno)); + FREEUP(proc->log_file); + FREEUP(proc->command); +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + return -1; + } + +#ifdef _WIN32 + // Windows实现 + SECURITY_ATTRIBUTES sa = { + .nLength = sizeof(SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE + }; + + // 创建管道 + HANDLE pipe_read, pipe_write; + if (!CreatePipe(&pipe_read, &pipe_write, &sa, 0)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to create pipe: %lu", GetLastError()); + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + LeaveCriticalSection(&manager->lock); + return -1; + } + + // 确保读取端不被继承 + SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0); + + STARTUPINFOA si = {0}; + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = pipe_write; + si.hStdError = pipe_write; + + PROCESS_INFORMATION pi; + + if (!CreateProcessA(NULL, + proc->command, + NULL, + NULL, + TRUE, + CREATE_NO_WINDOW, + NULL, + NULL, + &si, + &pi)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to create process: %lu", GetLastError()); + CloseHandle(pipe_read); + CloseHandle(pipe_write); + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + LeaveCriticalSection(&manager->lock); + return -1; + } + + CloseHandle(pipe_write); // 关闭写端 + proc->pipe_read = pipe_read; + proc->handle = pi.hProcess; + proc->status = 1; // 运行中 + proc->monitor_active = true; + + // 创建监控线程 + proc->monitor_thread = CreateThread(NULL, 0, output_monitor_thread, proc, 0, NULL); + if (!proc->monitor_thread) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to create monitor thread: %lu", GetLastError()); + + CloseHandle(pipe_read); + CloseHandle(pi.hProcess); + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + LeaveCriticalSection(&manager->lock); + return -1; + } + + CloseHandle(pi.hThread); +#else + // Linux实现 + pid_t pid = fork(); + if (pid == -1) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Fork failed: %s", strerror(errno)); + + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + pthread_mutex_unlock(&manager->lock); + return -1; + } + + if (pid == 0) { + // 子进程 + dup2(proc->output_fd, STDOUT_FILENO); + dup2(proc->output_fd, STDERR_FILENO); + close(proc->output_fd); + + char* args[] = {"/bin/sh", "-c", proc->command, NULL}; + execv("/bin/sh", args); + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to execute command: %s", strerror(errno)); + + _exit(1); + } else { + // 父进程 + XCECD_WORKER_LOG(XCE_LOG_DEBUG, "Process started successfully, output redirected to %s", proc->log_file); + + proc->handle = pid; + proc->status = 1; // 运行中 + + // 非阻塞等待子进程退出,避免僵尸进程 + XCECD_WORKER_LOG(XCE_LOG_DEBUG, "The pid of the child is: %d\n", pid); + } +#endif + + manager->process_count++; + manager->initialized = true; + +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, "Process started successfully"); + + FREEUP(proc->log_file); + + return 0; +} + +bool process_manager_update(process_manager_t* manager) { + //XCECD_WORKER_LOG(XCE_LOG_DEBUG, "Updating process manager..."); + if (!manager) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Invalid process manager"); + return false; + } + + int status; + pid_t pid; + int i; + + // 使用临时数组存储需要删除的进程索引 + int* remove_indices = calloc(1, manager->process_count * sizeof(int)); + int remove_count = 0; + +#ifdef _WIN32 + DWORD exit_code; + for (i = 0; i < manager->process_count; i++) { + if (GetExitCodeProcess(manager->processes[i].handle, &exit_code)) { + if (exit_code != STILL_ACTIVE) { + XCECD_WORKER_LOG(XCE_LOG_INFO, "Child %d exited with status %lu", + manager->processes[i].handle, exit_code); + + remove_indices[remove_count++] = i; + } + } else { + perror("GetExitCodeProcess"); + } + } +#else + for (i = 0; i < manager->process_count; i++) { + pid = waitpid(manager->processes[i].handle, &status, WNOHANG); + if (pid > 0) { + if (WIFEXITED(status)) { + XCECD_WORKER_LOG(XCE_LOG_INFO, "Child %d exited with status %d", + pid, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + XCECD_WORKER_LOG(XCE_LOG_INFO, "Child %d killed by signal %d", + pid, WTERMSIG(status)); + } + remove_indices[remove_count++] = i; + manager->processes[i].status = 0; + } + } + + /* handle this after receiving sigchld and avoid high cpu usage */ +#if 0 + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + for (i = 0; i < manager->process_count; i++) { + if (manager->processes[i].handle == pid) { + if (WIFEXITED(status)) { + XCECD_WORKER_LOG(XCE_LOG_INFO, "Child %d exited with status %d", + pid, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + XCECD_WORKER_LOG(XCE_LOG_INFO, "Child %d killed by signal %d", + pid, WTERMSIG(status)); + } + remove_indices[remove_count++] = i; + manager->processes[i].status = 0; + break; + } + } + } +#endif + + if (pid == -1 && errno != ECHILD) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Waitpid failed: %s", strerror(errno)); + } +#endif + + // 加锁 +#ifdef _WIN32 + EnterCriticalSection(&manager->lock); +#else + pthread_mutex_lock(&manager->lock); +#endif + + // 删除需要删除的进程信息 + for (i = 0; i < remove_count; i++) { + int index = remove_indices[i]; +#ifdef _WIN32 + CloseHandle(manager->processes[index].handle); +#endif + FREEUP(manager->processes[index].command); + FREEUP(manager->processes[index].log_file); + + // 将最后一个进程信息移动到当前的位置 + if (index != manager->process_count - 1) { + manager->processes[index] = manager->processes[manager->process_count - 1]; + } + + manager->process_count--; + } + + // 重新分配内存 + if (manager->process_count > 0) { + process_info_t* new_processes = realloc(manager->processes, manager->process_count * sizeof(process_info_t)); + if (new_processes) { + manager->processes = new_processes; + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed to reallocate memory for process info"); + // 如果 realloc 失败,保持原来的内存不变 + } + } else { + // 如果没有进程了,释放内存 + FREEUP(manager->processes); + } + + // 解锁 +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + + FREEUP(remove_indices); + + //XCECD_WORKER_LOG(XCE_LOG_DEBUG, "Process manager updated successfully"); + return true; +} + + +int get_process_count(process_manager_t* manager) { + if (!manager) { + return 0; + } + + int used_cores = 0; + + for (size_t i = 0; i < manager->process_count; i++) { + if (manager->processes[i].status == 1) { + used_cores++; + } + } + + return used_cores; +} + +int get_current_process_pid(process_manager_t* manager) { + XCECD_LOG(XCE_LOG_DEBUG, "Getting current process pid..."); + if (!manager || manager->process_count == 0) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid process manager or no process running"); + return -1; + } + + int current_index = manager->process_count - 1; + if (manager->processes[current_index].status == 1) { + return manager->processes[current_index].handle; + } + + return -1; +} + +process_info_t* process_manager_get_process_info(process_manager_t* manager, process_handle_t handle) { + XCECD_LOG(XCE_LOG_DEBUG, "Getting process info..."); + if (!manager) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid process manager"); + return NULL; + } + + for (size_t i = 0; i < manager->process_count; i++) { + if (manager->processes[i].handle == handle) { + return &manager->processes[i]; + } + } + + return NULL; +} + +void process_manager_destroy(process_manager_t* manager) { + XCECD_LOG(XCE_LOG_DEBUG, "Destroying process manager..."); + if (!manager) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid process manager"); + return; + } + + if (!manager->initialized) { + return; + } + + // FIX: add checking befor use manager->processes + for (size_t i = 0; i < manager->process_count; i++) { + process_info_t* proc = &manager->processes[i]; + +#ifdef _WIN32 + if (proc->status == 1) { // 如果进程还在运行 + proc->monitor_active = false; // 停止监控线程 + WaitForSingleObject(proc->monitor_thread, INFINITE); + CloseHandle(proc->monitor_thread); + CloseHandle(proc->pipe_read); + TerminateProcess(proc->handle, 1); + CloseHandle(proc->handle); + } +#else + if (proc->status == 1) { // 如果进程还在运行 + kill(proc->handle, SIGTERM); + waitpid(proc->handle, NULL, 0); + } +#endif + + if (proc->output_fd != -1) { + close(proc->output_fd); + } + FREEUP(proc->command); + FREEUP(proc->log_file); + FREEUP(proc->work_dir); + } + + FREEUP(manager->processes); + +#ifdef _WIN32 + DeleteCriticalSection(&manager->lock); +#else + pthread_mutex_destroy(&manager->lock); +#endif + + FREEUP(manager); +} + +int process_manager_remove_process(process_manager_t* manager, process_handle_t handle) { + XCECD_LOG(XCE_LOG_DEBUG, "Removing process..."); + if (!manager) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid process manager"); + return -1; + } + +#ifdef _WIN32 + EnterCriticalSection(&manager->lock); +#else + pthread_mutex_lock(&manager->lock); +#endif + + int index = -1; + for (size_t i = 0; i < manager->process_count; i++) { + if (manager->processes[i].handle == handle) { + index = i; + break; + } + } + + if (index == -1) { +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + return -1; + } + +#ifdef _WIN32 + CloseHandle(manager->processes[index].handle); +#endif + FREEUP(manager->processes[index].command); + + if (index != manager->process_count - 1) { + manager->processes[index] = manager->processes[manager->process_count - 1]; + } + + manager->process_count--; + + if (manager->process_count > 0) { + process_info_t* new_processes = realloc(manager->processes, manager->process_count * sizeof(process_info_t)); + if (new_processes) { + manager->processes = new_processes; + } else { + XCECD_LOG(XCE_LOG_ERROR, "Failed to reallocate memory for process info"); + } + } else { + FREEUP(manager->processes); + } + +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + XCECD_LOG(XCE_LOG_DEBUG, "Process removed successfully"); + + return 0; +} + +void process_manager_free(process_manager_t* manager) { + if (!manager) { + return; + } + + if (manager->processes) { + for (size_t i = 0; i < manager->process_count; i++) { + process_info_t* proc = &manager->processes[i]; + + FREEUP(proc->command); + FREEUP(proc->log_file); + +#ifdef _WIN32 + CloseHandle(proc->pipe_read); + CloseHandle(proc->monitor_thread); +#endif + close(proc->output_fd); + FREEUP(proc); + } + } + + FREEUP(manager->processes); + FREEUP(manager); + XCECD_LOG(XCE_LOG_DEBUG, "Process manager freed successfully"); +} \ No newline at end of file diff --git a/src/worker/src/process_worker_manager.c b/src/worker/src/process_worker_manager.c new file mode 100644 index 00000000..addd211b --- /dev/null +++ b/src/worker/src/process_worker_manager.c @@ -0,0 +1,364 @@ +/** + * @file process_worker_manager.c + * @brief This file contains the implementation of the worker process manager functions. + * It uses to manage worker process with resource limits. And it relies on process + * manager to manage the worker process. This module disabled by default. + */ +#include "process_worker_manager.h" +#include "xcelog.h" +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + + + +/** + * @brief start worker without resource limits (disabled by default) + * @param command command with arguments + * @return 0 if success, -1 if failed + */ +int process_manager_start_worker(process_manager_t* manager) { + XCECD_LOG(XCE_LOG_DEBUG, "Starting worker process"); + if (!manager) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid process manager and quit starting process."); + return -1; + } + +#ifdef _WIN32 + EnterCriticalSection(&manager->lock); +#else + pthread_mutex_lock(&manager->lock); +#endif + + process_info_t* new_processes = realloc(manager->processes, + (manager->process_count + 1) * sizeof(process_info_t)); + if (!new_processes) { +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + return -1; + } + manager->processes = new_processes; + + process_info_t* proc = &manager->processes[manager->process_count]; + process_info_init(proc); + proc->command = strdup("xcecd_worker"); + proc->log_file = strdup("/tmp/xcecd_worker.log"); + + proc->output_fd = open(proc->log_file, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (proc->output_fd == -1) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to open log file: %s", strerror(errno)); + FREEUP(proc->log_file); + FREEUP(proc->command); + +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + return -1; + } + +#ifdef _WIN32 + SECURITY_ATTRIBUTES sa = { + .nLength = sizeof(SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE + }; + + HANDLE pipe_read, pipe_write; + if (!CreatePipe(&pipe_read, &pipe_write, &sa, 0)) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create pipe: %lu", GetLastError()); + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + LeaveCriticalSection(&manager->lock); + return -1; + } + + SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0); + + STARTUPINFOA si = {0}; + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = pipe_write; + si.hStdError = pipe_write; + + PROCESS_INFORMATION pi; + + if (!CreateProcessA(NULL, + proc->command, + NULL, + NULL, + TRUE, + CREATE_NO_WINDOW, + NULL, + NULL, + &si, + &pi)) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create process: %lu", GetLastError()); + CloseHandle(pipe_read); + CloseHandle(pipe_write); + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + LeaveCriticalSection(&manager->lock); + return -1; + } + + CloseHandle(pipe_write); + proc->pipe_read = pipe_read; + proc->handle = pi.hProcess; + proc->status = 1; + proc->monitor_active = true; + + proc->monitor_thread = CreateThread(NULL, 0, output_monitor_thread, proc, 0, NULL); + if (!proc->monitor_thread) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create monitor thread: %lu", GetLastError()); + CloseHandle(pipe_read); + CloseHandle(pi.hProcess); + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + LeaveCriticalSection(&manager->lock); + return -1; + } + + CloseHandle(pi.hThread); +#else + pid_t pid = fork(); + if (pid == -1) { + XCECD_LOG(XCE_LOG_ERROR, "Fork failed: %s", strerror(errno)); + close(proc->output_fd); + FREEUP(proc->log_file); + FREEUP(proc->command); + pthread_mutex_unlock(&manager->lock); + return -1; + } + + if (pid == 0) { + dup2(proc->output_fd, STDOUT_FILENO); + dup2(proc->output_fd, STDERR_FILENO); + close(proc->output_fd); + + char* xce_server_dir = getenv("XCE_SERVER_DIR"); + char worker_path[1024]; + + if (xce_server_dir != NULL) { + + snprintf(worker_path, sizeof(worker_path), "%s/xcecd_worker", xce_server_dir); + + if (access(worker_path, X_OK) != 0) { + XCECD_LOG(XCE_LOG_WARN, "xcecd_worker not found or not executable in XCE_SERVER_DIR: %s", + xce_server_dir); + } + } else { + XCECD_LOG(XCE_LOG_WARN, "XCE_SERVER_DIR not set, using default path for xcecd_worker"); + } + + execl(worker_path, proc->command, NULL); + + XCECD_LOG(XCE_LOG_ERROR, "Failed to execute command: %s", strerror(errno)); + _exit(1); + } else { + /* parent */ + XCECD_LOG(XCE_LOG_DEBUG, "Process started successfully, output redirected to %s", proc->log_file); + proc->handle = pid; + proc->status = 1; + } + + close(proc->output_fd); + +#endif + + manager->process_count++; + manager->initialized = true; + +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + + XCECD_LOG(XCE_LOG_DEBUG, "Worker process started successfully"); + return 0; +} + + +/** + * @brief update the process status in the list + * @param manager process manager + * @param worker worker process + * @param job XCE job information + * @return 0 if success, -1 if failed + */ +bool worker_process_update(process_manager_t* manager, + xcecd_worker_t* worker, + xcecd_job_t* job, + sqlite3* db) +{ + XCECD_LOG(XCE_LOG_DEBUG, "Updating worker process..."); + if (!manager) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid process manager"); + return false; + } + + int status; + pid_t pid; + int i; + + int* remove_indices = calloc(1, manager->process_count * sizeof(int)); + if (!remove_indices) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for remove indices"); + return false; + } + int remove_count = 0; + +#ifdef _WIN32 + DWORD exit_code; + for (i = 0; i < manager->process_count; i++) { + if (GetExitCodeProcess(manager->processes[i].handle, &exit_code)) { + if (exit_code != STILL_ACTIVE) { + XCECD_LOG(XCE_LOG_INFO, "Child %d exited with status %lu", + manager->processes[i].handle, exit_code); + if (exit_code == 0) { + update_worker_status(worker, + manager->processes[i].handle, + WORKER_DONE, + job, + db); + + } else { + update_worker_status(worker, + manager->processes[i].handle, + WORKER_EXITED, + job, + db); + } + remove_indices[remove_count++] = i; + } + } else { + XCECD_LOG(XCE_LOG_ERROR, "GetExitCodeProcess failed: %lu", GetLastError()); + } + } +#else + for (i = 0; i < manager->process_count; i++) { + pid = waitpid(manager->processes[i].handle, &status, WNOHANG); + if (pid > 0) { + if (WIFEXITED(status)) { + XCECD_LOG(XCE_LOG_INFO, "Child %d exited with status %d", + pid, WEXITSTATUS(status)); + update_worker_status(worker, + pid, + WORKER_DONE, + job, + db); + + } else if (WIFSIGNALED(status)) { + XCECD_LOG(XCE_LOG_INFO, "Child %d killed by signal %d", + pid, WTERMSIG(status)); + update_worker_status(worker, + pid, + WORKER_EXITED, + job, + db); + } + remove_indices[remove_count++] = i; + manager->processes[i].status = 0; + } + } + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + for (i = 0; i < manager->process_count; i++) { + if (manager->processes[i].handle == pid) { + if (WIFEXITED(status)) { + XCECD_LOG(XCE_LOG_INFO, "Child %d exited with status %d", + pid, WEXITSTATUS(status)); + update_worker_status(worker, + pid, + WORKER_DONE, + job, + db); + } else if (WIFSIGNALED(status)) { + XCECD_LOG(XCE_LOG_INFO, "Child %d killed by signal %d", + pid, WTERMSIG(status)); + update_worker_status(worker, + pid, + WORKER_EXITED, + job, + db); + + } + remove_indices[remove_count++] = i; + manager->processes[i].status = 0; + break; + } + } + } + + if (pid == -1 && errno != ECHILD) { + XCECD_LOG(XCE_LOG_ERROR, "Waitpid failed: %s", strerror(errno)); + } +#endif + +#ifdef _WIN32 + EnterCriticalSection(&manager->lock); +#else + pthread_mutex_lock(&manager->lock); +#endif + + for (i = 0; i < remove_count; i++) { + int index = remove_indices[i]; +#ifdef _WIN32 + CloseHandle(manager->processes[index].handle); +#endif + FREEUP(manager->processes[index].command); + FREEUP(manager->processes[index].log_file); + + if (index != manager->process_count - 1) { + manager->processes[index] = manager->processes[manager->process_count - 1]; + } + + manager->process_count--; + } + + if (manager->process_count > 0) { + process_info_t* new_processes = realloc(manager->processes, + manager->process_count * sizeof(process_info_t)); + if (new_processes) { + manager->processes = new_processes; + } else { + XCECD_LOG(XCE_LOG_ERROR, "Failed to reallocate memory for process info"); + } + } else { + FREEUP(manager->processes); + } + +#ifdef _WIN32 + LeaveCriticalSection(&manager->lock); +#else + pthread_mutex_unlock(&manager->lock); +#endif + + FREEUP(remove_indices); + + XCECD_LOG(XCE_LOG_DEBUG, "Worker process updated successfully"); + return true; +} \ No newline at end of file diff --git a/src/worker/src/resource_sync.c b/src/worker/src/resource_sync.c new file mode 100644 index 00000000..918ebcfe --- /dev/null +++ b/src/worker/src/resource_sync.c @@ -0,0 +1,131 @@ +#include "resource_sync.h" +#include "xcecd.h" +#include "xcecd_utils.h" +#include "node_information.h" +#include "job_information.h" +#include "xcelog.h" +#include +#include +#include +#include +#include + +#define SYNC_INTERVAL 1 + +struct resource_sync { + pthread_t thread; + _Atomic bool running; + _Atomic bool updating; + struct cluster_node *my_cluster; + struct host_node *my_node; + struct client_node *my_client; +}; + +static void* resource_sync_thread(void* arg) { + resource_sync_t* sync = (resource_sync_t*)arg; + while (atomic_load(&sync->running)) { + + /* TODO:资源更新,添加逻辑 */ + + resource_sync_update(sync); + + /* TODO:资源汇总,添加逻辑 */ + + sleep(SYNC_INTERVAL); + } + return NULL; +} + +resource_sync_t* resource_sync_create(void) { + XCECD_LOG(XCE_LOG_DEBUG, "Creating resource sync module..."); + resource_sync_t* sync = (resource_sync_t*)calloc(1, sizeof(resource_sync_t)); + if (sync == NULL) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create resource sync module"); + return NULL; + } + atomic_store(&sync->running, false); + atomic_store(&sync->updating, false); + sync->my_cluster = NULL; + sync->my_node = host_node_create(); + sync->my_client = NULL; + sync->thread = 0; + + XCECD_LOG(XCE_LOG_DEBUG, "Resource sync module created successfully"); + return sync; +} + +void resource_sync_destroy(resource_sync_t* sync) { + XCECD_LOG(XCE_LOG_DEBUG, "Destroying resource sync module..."); + if (sync == NULL) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid resource sync module"); + return; + } + + /* 资源销毁 TODO:先释放host,client等结构 */ + FREEUP(sync->my_node); + FREEUP(sync); +} + +bool resource_sync_start(resource_sync_t* sync) { + XCECD_LOG(XCE_LOG_DEBUG, "Starting resource sync module..."); + if (sync == NULL) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid resource sync module"); + return false; + } + + atomic_store(&sync->running, true); + if (pthread_create(&sync->thread, NULL, resource_sync_thread, sync) != 0) { + atomic_store(&sync->running, false); + return false; + } + + XCECD_LOG(XCE_LOG_DEBUG, "Resource sync module started successfully"); + return true; +} + +void resource_sync_stop(resource_sync_t* sync) { + XCECD_LOG(XCE_LOG_DEBUG, "Stopping resource sync module..."); + if (sync == NULL) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid resource sync module"); + return; + } + atomic_store(&sync->running, false); + if (sync->thread != 0) { + pthread_join(sync->thread, NULL); + } + XCECD_LOG(XCE_LOG_DEBUG, "Resource sync module stopped successfully"); +} + +bool resource_sync_update(resource_sync_t* sync) { + //XCECD_LOG(XCE_LOG_DEBUG, "Updating resource sync module..."); + if (sync == NULL) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid resource sync module"); + return false; + } + + atomic_store(&sync->updating, true); + + /* TODO:资源更新,添加逻辑 */ + sync->my_node->node_status.max_cpu_cores = hardware_monitor_get_cpu_cores(xcecd_ctx.hardware_monitor); + + atomic_store(&sync->updating, false); + + //XCECD_LOG(XCE_LOG_DEBUG, "Resource sync module updated successfully"); + return true; +} + +void resource_sync_read(resource_sync_t* sync, host_node_t** node) { + if (!sync) { + return; + } + + while (atomic_load(&sync->updating)) { + /* 等待更新完成 */ + usleep(1000); + } + + /* 读取资源同步状态 + * TODO: 添加读取逻辑 + */ + *node = sync->my_node; +} \ No newline at end of file diff --git a/src/worker/src/static_array.c b/src/worker/src/static_array.c new file mode 100644 index 00000000..4cfcf6df --- /dev/null +++ b/src/worker/src/static_array.c @@ -0,0 +1,75 @@ +#include "static_array.h" + + +void init_static_array(StaticArray* arr, size_t element_size) { + arr->count = 0; + arr->element_size = element_size; + memset(arr->data, 0, sizeof(arr->data)); + pthread_mutex_init(&arr->lock, NULL); +} + +bool push_static(StaticArray* arr, void* value) { + pthread_mutex_lock(&arr->lock); + if (arr->count >= MAX_ARRAY_SIZE) { + pthread_mutex_unlock(&arr->lock); + return false; + } + size_t offset = arr->count * arr->element_size; + memcpy(arr->data + offset, value, arr->element_size); + arr->count++; + pthread_mutex_unlock(&arr->lock); + return true; +} + +bool pop_static(StaticArray* arr, void* out_value) { + pthread_mutex_lock(&arr->lock); + if (arr->count == 0) { + pthread_mutex_unlock(&arr->lock); + return false; + } + size_t offset = (arr->count - 1) * arr->element_size; + memcpy(out_value, arr->data + offset, arr->element_size); + arr->count--; + pthread_mutex_unlock(&arr->lock); + return true; +} + +bool pop_static_from_bottom(StaticArray* arr, void* out_value) { + pthread_mutex_lock(&arr->lock); + if (arr->count == 0) { + pthread_mutex_unlock(&arr->lock); + return false; + } + memcpy(out_value, arr->data, arr->element_size); + + for (size_t i = 1; i < arr->count; i++) { + size_t src_offset = i * arr->element_size; + size_t dest_offset = (i - 1) * arr->element_size; + memcpy(arr->data + dest_offset, arr->data + src_offset, arr->element_size); + } + arr->count--; + pthread_mutex_unlock(&arr->lock); + return true; +} + +void clear_static(StaticArray* arr) { + pthread_mutex_lock(&arr->lock); + arr->count = 0; + pthread_mutex_unlock(&arr->lock); +} + +bool get_element(StaticArray* arr, size_t index, void* out_value) { + pthread_mutex_lock(&arr->lock); + if (index >= arr->count) { + pthread_mutex_unlock(&arr->lock); + return false; + } + size_t offset = index * arr->element_size; + memcpy(out_value, arr->data + offset, arr->element_size); + pthread_mutex_unlock(&arr->lock); + return true; +} + +void destroy_static_array(StaticArray* arr) { + pthread_mutex_destroy(&arr->lock); +} \ No newline at end of file diff --git a/src/worker/src/system_monitor.c b/src/worker/src/system_monitor.c new file mode 100644 index 00000000..a805db6f --- /dev/null +++ b/src/worker/src/system_monitor.c @@ -0,0 +1,133 @@ +#include "system_monitor.h" +#include "xcelog.h" +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + +struct system_monitor { + system_status_t status; + bool running; +#ifdef _WIN32 + PDH_HQUERY cpu_query; + PDH_HCOUNTER cpu_counter; +#endif +}; + +system_monitor_t* system_monitor_create(void) { + XCECD_LOG(XCE_LOG_DEBUG, "Creating system monitor..."); + system_monitor_t* monitor = calloc(1, sizeof(system_monitor_t)); + if (!monitor) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create system monitor"); + return NULL; + } + + monitor->running = true; + +#ifdef _WIN32 + // 初始化CPU性能计数器 + if (PdhOpenQuery(NULL, 0, &monitor->cpu_query) != ERROR_SUCCESS) { + free(monitor); + return NULL; + } + + if (PdhAddCounter(monitor->cpu_query, "\\Processor(_Total)\\% Processor Time", + 0, &monitor->cpu_counter) != ERROR_SUCCESS) { + PdhCloseQuery(monitor->cpu_query); + free(monitor); + return NULL; + } +#endif + + XCECD_LOG(XCE_LOG_DEBUG, "System monitor created successfully"); + return monitor; +} + +const system_status_t* system_monitor_get_status(system_monitor_t* monitor) { + return monitor ? &monitor->status : NULL; +} + +bool system_monitor_update(system_monitor_t* monitor) { + XCECD_LOG(XCE_LOG_DEBUG, "Updating system monitor..."); + if (!monitor) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid system monitor"); + return false; + } + +#ifdef _WIN32 + // 获取CPU使用率 + PDH_FMT_COUNTERVALUE counter_value; + PdhCollectQueryData(monitor->cpu_query); + PdhGetFormattedCounterValue(monitor->cpu_counter, PDH_FMT_DOUBLE, NULL, &counter_value); + monitor->status.cpu_usage = counter_value.doubleValue; + + // 获取内存信息 + MEMORYSTATUSEX mem_info; + mem_info.dwLength = sizeof(MEMORYSTATUSEX); + GlobalMemoryStatusEx(&mem_info); + monitor->status.memory_total = mem_info.ullTotalPhys; + monitor->status.memory_used = mem_info.ullTotalPhys - mem_info.ullAvailPhys; + + // 获取磁盘信息 + ULARGE_INTEGER free_bytes, total_bytes; + if (GetDiskFreeSpaceEx("C:\\", &free_bytes, &total_bytes, NULL)) { + monitor->status.disk_total = total_bytes.QuadPart; + monitor->status.disk_used = total_bytes.QuadPart - free_bytes.QuadPart; + } +#else + // 获取系统信息 + struct sysinfo si; + if (sysinfo(&si) == 0) { + monitor->status.memory_total = si.totalram; + monitor->status.memory_used = si.totalram - si.freeram; + } + + // 获取CPU使用率 + FILE* file = fopen("/proc/stat", "r"); + if (file) { + unsigned long long user, nice, system, idle; + fscanf(file, "cpu %llu %llu %llu %llu", &user, &nice, &system, &idle); + fclose(file); + + unsigned long long total = user + nice + system + idle; + unsigned long long work = user + nice + system; + + monitor->status.cpu_usage = ((double)work / total) * 100.0; + } + + // 获取磁盘信息 + struct statvfs fs; + if (statvfs("/", &fs) == 0) { + monitor->status.disk_total = fs.f_blocks * fs.f_frsize; + monitor->status.disk_used = (fs.f_blocks - fs.f_bfree) * fs.f_frsize; + } +#endif + XCECD_LOG(XCE_LOG_DEBUG, "System monitor updated successfully"); + return true; +} + +void system_monitor_destroy(system_monitor_t* monitor) { + XCECD_LOG(XCE_LOG_DEBUG, "Destroying system monitor..."); + if (!monitor) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid system monitor"); + return; + } + + monitor->running = false; + +#ifdef _WIN32 + PdhCloseQuery(monitor->cpu_query); +#endif + + free(monitor); +} diff --git a/src/worker/src/thread_pool.c b/src/worker/src/thread_pool.c new file mode 100644 index 00000000..8d632d9f --- /dev/null +++ b/src/worker/src/thread_pool.c @@ -0,0 +1,332 @@ +#include "thread_pool.h" +#include "xcecd_utils.h" +#include "xcelog.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + +/* get current thread id */ +static long get_system_tid() { +#ifdef _WIN32 + return GetCurrentThreadId(); +#else + return syscall(SYS_gettid); +#endif +} + +#if 0 +static int queue_is_empty(message_queue_t* queue) { + return queue->count == 0; +} +#endif + +static int queue_is_empty(message_queue_t* queue) { + return queue->count == 0; +} + +static void* thread_pool_worker(void* arg) { + XCECD_LOG(XCE_LOG_DEBUG, "Thread pool worker starting..."); + thread_pool_t* pool = (thread_pool_t*)arg; + job_control_task_t task; + size_t i; + + long sys_tid = get_system_tid(); + + // 找到第一个空闲的映射槽位 + for (i = 0; i < pool->num_threads; i++) { + if (pool->thread_map[i].system_tid == 0) { + pool->thread_map[i].system_tid = sys_tid; + pool->thread_map[i].pool_tid = i + 1; // 线程ID从1开始 + break; + } + } + +#if 1 + while (pool->running) { + message_queue_t* queue = &pool->control_queue; + + pthread_mutex_lock(&queue->lock); + while (queue_is_empty(queue) && pool->running) { + pthread_cond_wait(&queue->not_empty, &queue->lock); + } + + /* we need to unlock the queue before calling queue_pop() */ + pthread_mutex_unlock(&queue->lock); + if (!pool->running) { + break; + } + + if (queue_pop(queue, &task) == 0) { + // handle job control task + XCECD_LOG(XCE_LOG_DEBUG, "Processing job control task in thread %zu, system_tid: %ld", + pool->thread_map[i].pool_tid, sys_tid); + task.func(&task); + } + } +#endif + + return NULL; +} + +message_queue_t* message_queue_create(size_t capacity, size_t msg_size) { + XCECD_LOG(XCE_LOG_DEBUG, "Creating message queue..."); + message_queue_t* queue = calloc(1, sizeof(message_queue_t)); + if (!queue) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create message queue"); + return NULL; + } + + queue->messages = calloc(capacity, msg_size); + if (!queue->messages) { + FREEUP(queue); + return NULL; + } + + queue->func = NULL; + queue->capacity = capacity; + queue->msg_size = msg_size; + queue->head = 0; + queue->tail = 0; + queue->count = 0; + + pthread_mutex_init(&queue->lock, NULL); + pthread_cond_init(&queue->not_empty, NULL); + pthread_cond_init(&queue->not_full, NULL); + + return queue; +} + +void message_queue_destroy(message_queue_t* queue) { + XCECD_LOG(XCE_LOG_DEBUG, "Destroying message queue..."); + if (!queue) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid message queue"); + return; + } + + pthread_mutex_destroy(&queue->lock); + pthread_cond_destroy(&queue->not_empty); + pthread_cond_destroy(&queue->not_full); + + FREEUP(queue->messages); + FREEUP(queue->func); +} + +int queue_push(message_queue_t* queue, void* message) { + XCECD_LOG(XCE_LOG_DEBUG, "Pushing message to queue..."); + int err = 0; + + pthread_mutex_lock(&(queue->lock)); + + while (queue->count == queue->capacity) { + pthread_cond_wait(&(queue->not_full), &(queue->lock)); + XCECD_LOG(XCE_LOG_DEBUG, "Message queue is full, waiting..."); + } + + char *dest = (char*)queue->messages + (queue->tail * queue->msg_size); + memcpy(dest, message, queue->msg_size); + FREEUP(message); + + queue->tail = (queue->tail + 1) % queue->capacity; + queue->count += 1; + + XCECD_LOG(XCE_LOG_DEBUG, "Message added to queue. Total messages: %zu", queue->count); + + // 通知等待的线程有新消息 + if (pthread_cond_signal(&(queue->not_empty)) != 0) { + err = -1; + } + + if (pthread_mutex_unlock(&(queue->lock)) != 0) { + err = -1; + } + + return err; +} + +int queue_pop(message_queue_t* queue, void* message) { + //XCECD_LOG(XCE_LOG_DEBUG, "Popping message from queue..."); + int err = 0; + + pthread_mutex_lock(&(queue->lock)); + + while (queue->count == 0) { + // 需要退出等待下一次主进程housekeeping + pthread_mutex_unlock(&(queue->lock)); + return 1; + } + + char *src = (char*)queue->messages + (queue->head * queue->msg_size); + memcpy(message, src, queue->msg_size); + + queue->head = (queue->head + 1) % queue->capacity; + queue->count -= 1; + + XCECD_LOG(XCE_LOG_DEBUG, "Message popped from queue. Remaining messages: %zu", queue->count); + + // 通知等待的线程有空间 + if (pthread_cond_signal(&(queue->not_full)) != 0) { + err = -1; + } + + if (pthread_mutex_unlock(&(queue->lock)) != 0) { + err = -1; + } + + return err; +} + +thread_pool_t* thread_pool_create(size_t num_threads) { + thread_pool_t* pool = calloc(1, sizeof(thread_pool_t)); + if (!pool) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create thread pool"); + return NULL; + } + XCECD_LOG(XCE_LOG_DEBUG, "Creating thread pool..."); + + pool->num_threads = num_threads; + pool->running = true; + + // 分配线程ID映射表 + pool->thread_map = calloc(num_threads, sizeof(thread_id_map_t)); + if (!pool->thread_map) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create thread ID map"); + free(pool); + return NULL; + } + + // 创建fork消息队列 + message_queue_t* fork_queue = message_queue_create(MAX_QUEUE_SIZE, sizeof(fork_task_t)); + if (!fork_queue) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create fork message queue"); + FREEUP(pool->thread_map); + FREEUP(pool); + return NULL; + } + memcpy(&pool->fork_queue, fork_queue, sizeof(message_queue_t)); + FREEUP(fork_queue); + + // 创建control消息队列 + message_queue_t* control_queue = message_queue_create(MAX_QUEUE_SIZE, sizeof(job_control_task_t)); + if (!control_queue) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create control message queue"); + message_queue_destroy(&pool->fork_queue); + FREEUP(pool->thread_map); + FREEUP(pool); + FREEUP(fork_queue); + return NULL; + } + memcpy(&pool->control_queue, control_queue, sizeof(message_queue_t)); + FREEUP(control_queue); + +#ifdef _WIN32 + pool->threads = calloc(num_threads, sizeof(HANDLE)); +#else + pool->threads = calloc(num_threads, sizeof(pthread_t)); +#endif + + if (!pool->threads) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create thread pool"); + message_queue_destroy(&pool->fork_queue); + message_queue_destroy(&pool->control_queue); + FREEUP(pool->thread_map); + FREEUP(pool); + return NULL; + } + + // 创建工作线程 + for (size_t i = 0; i < num_threads; i++) { +#ifdef _WIN32 + pool->threads[i] = CreateThread(NULL, 0, thread_pool_worker, pool, 0, NULL); + if (!pool->threads[i]) { +#else + if (pthread_create(&pool->threads[i], NULL, thread_pool_worker, pool) != 0) { +#endif + XCECD_LOG(XCE_LOG_ERROR, "Failed to create thread %zu", i); + thread_pool_destroy(pool); + return NULL; + + } + } + + return pool; +} + + +void thread_pool_destroy(thread_pool_t* pool) { + XCECD_LOG(XCE_LOG_DEBUG, "Destroying thread pool..."); + if (!pool) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid thread pool"); + return; + } + + pool->running = false; + + // 唤醒所有等待的线程 + pthread_mutex_lock(&pool->control_queue.lock); + pthread_cond_broadcast(&pool->control_queue.not_empty); + pthread_mutex_unlock(&pool->control_queue.lock); + + // 等待所有线程结束 + for (size_t i = 0; i < pool->num_threads; i++) { +#ifdef _WIN32 + WaitForSingleObject(pool->threads[i], INFINITE); + CloseHandle(pool->threads[i]); +#else + pthread_join(pool->threads[i], NULL); +#endif + } + + message_queue_destroy(&pool->fork_queue); + message_queue_destroy(&pool->control_queue); + + FREEUP(pool->threads); + FREEUP(pool->thread_map); + FREEUP(pool); +} + +int submit_fork_task(thread_pool_t* pool, fork_task_t* task) { + XCECD_LOG(XCE_LOG_DEBUG, "Submitting fork task..."); + if (!pool || !task) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid thread pool or task"); + return -1; + } + + return queue_push(&pool->fork_queue, task); +} + +int submit_job_control_task(thread_pool_t* pool, job_control_task_t* task) { + XCECD_LOG(XCE_LOG_DEBUG, "Submitting job control task..."); + if (!pool || !task) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid thread pool or task"); + return -1; + } + + return queue_push(&pool->control_queue, task); +} + +void job_control_signal(void* arg) { + job_control_task_t* task = (job_control_task_t*)arg; + XCECD_LOG(XCE_LOG_DEBUG, "Handling job control task..."); + if (!task) { + return; + } + + /* send signal to job by pid */ + /* use -task->target_pid to kill the process group for running job */ + if (kill(-task->target_pid, task->signal) == -1) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to send signal to job %d: %s", + task->target_pid, strerror(errno)); + } +} diff --git a/src/worker/src/uv_ipc.c b/src/worker/src/uv_ipc.c new file mode 100644 index 00000000..260789b7 --- /dev/null +++ b/src/worker/src/uv_ipc.c @@ -0,0 +1,1508 @@ +/** + * @file uv_ipc.c + * @brief IPC worker and basic function implementation using libuv + * to decouple the communication between master and worker + */ +#include "uv_ipc.h" +#include "xcecd.h" +#include "xcelog.h" +#include +#include +#include +#include + + + +int exit_flag = 0; +uv_loop_t* loop = NULL; +static char socket_path[256]; + +static uv_pipe_t master_pipe; +static job_status_callback_t master_status_callback = NULL; +static client_disconnect_callback_t master_disconnect_callback = NULL; +static heartbeat_callback_t master_heartbeat_callback = NULL; +static uint64_t next_client_id = 1; + +static uv_pipe_t worker_pipe; +static job_callback_t worker_job_callback = NULL; +static connection_callback_t worker_connection_callback = NULL; +static bool worker_connected = false; +static uint64_t worker_id = 0; +static bool job_requested = false; + +static worker_reconnect_callback_t worker_reconnect_callback = NULL; +static job_signal_callback_t worker_signal_callback = NULL; +static job_ckpt_callback_t worker_ckpt_callback = NULL; +static job_ckpt_result_callback_t master_ckpt_result_callback = NULL; + +static uv_timer_t* exit_timer = NULL; + +typedef struct client_s { + uint64_t id; + uv_pipe_t* pipe; + uint64_t current_job_id; + pid_t worker_pid; + struct client_s* next; +} client_t; + +typedef struct job_client_map_s { + uint64_t job_id; + client_t* client; + struct job_client_map_s* next; +} job_client_map_t; + +static client_t* clients = NULL; +static job_client_map_t* job_client_map = NULL; + + +/** + * @brief add job to client mapping + * @param job_id + * @param client + * @return void + */ +__attribute__((unused)) static void add_job_client_mapping(uint64_t job_id, client_t* client) { + job_client_map_t* mapping = (job_client_map_t*)calloc(1, sizeof(job_client_map_t)); + if (!mapping) return; + + mapping->job_id = job_id; + mapping->client = client; + mapping->next = job_client_map; + job_client_map = mapping; + + client->current_job_id = job_id; + + XCECD_LOG(XCE_LOG_DEBUG, + "Added job %lu to client %lu mapping", + job_id, client->id); +} + +/** + * @brief remove job to client mapping + * @param job_id + * @return void + */ +__attribute__((unused)) static void remove_job_client_mapping(uint64_t job_id) { + job_client_map_t* current = job_client_map; + job_client_map_t* prev = NULL; + + while (current) { + if (current->job_id == job_id) { + if (prev) { + prev->next = current->next; + } else { + job_client_map = current->next; + } + + if (current->client) { + current->client->current_job_id = 0; + } + + XCECD_LOG(XCE_LOG_DEBUG, + "Removed job %lu from client mapping", + job_id); + + free(current); + return; + } + prev = current; + current = current->next; + } +} + +/** + * @brief find client by job_id + * @param job_id + * @return client_t* + */ +static client_t* find_client_by_job_id(uint64_t job_id) { + job_client_map_t* current = job_client_map; + + while (current) { + if (current->job_id == job_id) { + return current->client; + } + current = current->next; + } + + return NULL; +} + +/** + * @brief check if the master is connected to the worker + * @return bool + */ +bool ipc_master_check_connection() { +#ifdef VIRTUAL_CLUSTER + return true; +#endif + + if (access(socket_path, F_OK) == -1) { + XCECD_LOG(XCE_LOG_ERROR, + "Socket file %s does not exist: %s", + socket_path, + strerror(errno)); + return false; + } + + if (access(socket_path, R_OK | W_OK) == -1) { + XCECD_LOG(XCE_LOG_ERROR, + "Socket file %s is not readable or writable: %s", + socket_path, + strerror(errno)); + return false; + } + + if (!uv_is_active((uv_handle_t*)&master_pipe)) { + XCECD_LOG(XCE_LOG_DEBUG, "Master pipe is not active."); + return false; + } + + int fd = -1; + if (uv_fileno((uv_handle_t*)&master_pipe, &fd) == 0) { + if (fd < 0) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid file descriptor."); + return false; + } + } else { + XCECD_LOG(XCE_LOG_ERROR, "Failed to get file descriptor."); + return false; + } + + return true; +} + +/** + * @brief check if the worker is connected to the master + * @return bool + */ +bool ipc_worker_check_connection() { + + if (!uv_is_active((uv_handle_t*)&worker_pipe)) { + worker_connected = false; + } + + int fd; + if (uv_fileno((uv_handle_t*)&worker_pipe, &fd) == 0) { + if (fd < 0) { + worker_connected = false; + } + } else { + worker_connected = false; + } + + char buffer[1]; + int ret = recv(fd, + buffer, + sizeof(buffer), + MSG_PEEK | MSG_DONTWAIT); + + if (ret == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + worker_connected = true; + } else { + worker_connected = false; + } + } else if (ret == 0) { + worker_connected = false; + } + + if (worker_reconnect_callback) { + worker_reconnect_callback(worker_connected); + } + + return worker_connected; +} + +/** + * @brief add new client + * @param pipe + * @return client_t* + */ +static client_t* add_client(uv_pipe_t* pipe) { + client_t* client = (client_t*)calloc(1, sizeof(client_t)); + if (!client) return NULL; + + client->id = next_client_id++; + client->pipe = pipe; + client->next = clients; + clients = client; + return client; +} + +/** + * @brief find client + * @param pipe + * @return client_t* + */ +client_t* find_client(uv_pipe_t* pipe) { + client_t* current = clients; + while (current) { + if (current->pipe == pipe) { + return current; + } + current = current->next; + } + return NULL; +} + +/** + * @brief remove client + * @param pipe + * @return void + */ +static void remove_client(uv_pipe_t* pipe) { + client_t* current = clients; + client_t* prev = NULL; + + while (current) { + if (current->pipe == pipe) { + if (prev) { + prev->next = current->next; + } else { + clients = current->next; + } + + if (master_disconnect_callback) { + master_disconnect_callback(current->id); + } + + free(current); + current = NULL; + return; + } + prev = current; + current = current->next; + } +} + +/** + * @brief close handle and free memory (callback) + * @param handle + * @return void + */ +static void on_handle_close(uv_handle_t* handle) { + if (handle && handle != (uv_handle_t*)&worker_pipe && + handle != (uv_handle_t*)&master_pipe) { + free(handle); + } +} + +/** + * @brief alloc buffer + * @param handle + * @param suggested_size + */ +static void alloc_buffer(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) +{ + buf->base = (char*)calloc(1, suggested_size); + if (buf->base) { + buf->len = suggested_size; + } else { + buf->len = 0; + } +} + +/** + * @brief write then free request (callback) + * @param req + * @param status + */ +void on_write_complete(uv_write_t* req, + int status) +{ + if (status < 0) { + worker_connected = false; + } + if (req) { + free(req); + } +} + +/** + * @brief check exit condition (callback) + * @param timer + */ +__attribute__((unused)) static void check_exit_condition(uv_timer_t* timer) { +#if 0 + if (child_processes <= 0) { + if (active_jobs > 0) { + active_jobs = 0; + } + + if (active_jobs <= 0 && !exit_flag) { + exit_flag = 1; + } +} +#endif + if (exit_flag) { + XCECD_LOG(XCE_LOG_INFO, "Stop the main event loop."); + uv_timer_stop(timer); + uv_stop(loop); + } +} + +/** + * @brief on master read (callback) + * @param client + * @param nread + * @param buf + * @return void + */ +static void on_master_read(uv_stream_t* client, + ssize_t nread, + const uv_buf_t* buf) +{ + XCECD_LOG(XCE_LOG_DEBUG, "Received message from client."); + +#ifndef IPC_WORKER + if (nread < 0) { + if (nread != UV_EOF) { + XCECD_LOG(XCE_LOG_ERROR, + "Read error: %s\n", + uv_strerror(nread)); + } else { + XCECD_LOG(XCE_LOG_DEBUG, "Client disconnected."); + + client_t* disconnected_client = find_client((uv_pipe_t*)client); + if (disconnected_client && disconnected_client->current_job_id > 0) { + remove_job_client_mapping(disconnected_client->current_job_id); + } + + remove_client((uv_pipe_t*)client); + uv_close((uv_handle_t*)client, on_handle_close); + } + + if (buf->base) { + free(buf->base); + } + return; + } + + if (nread == 0) { + if (buf->base) { + free(buf->base); + } + return; + } + + if (nread >= sizeof(ipc_message_t)) { + ipc_message_t* msg = (ipc_message_t*)buf->base; + + if (!msg) { + XCECD_LOG(XCE_LOG_ERROR, + "Error: Received null message."); + free(buf->base); + return; + } + + if (msg->type == MSG_JOB_STATUS || + msg->type == MSG_JOB_COMPLETED) { + msg->data.status.status_message[sizeof(msg->data.status.status_message) - 1] = '\0'; + } + + if (msg->type == MSG_GET_JOB) { + ipc_message_t response; + xcecd_worker_t *worker; + client_t* requesting_client = find_client((uv_pipe_t*)client); + + int worker_pid = msg->worker_pid; + if (worker_pid <= 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Invalid worker PID: %d", + worker_pid); + return; + } + + if (requesting_client) { + requesting_client->worker_pid = worker_pid; + } + + worker = find_worker_by_pid(xcecd_ctx.worker_list, + worker_pid); + + if (!worker) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to find worker for PID %d", + worker_pid); + return; + /* ToDo: Handle worker not found - kill job */ + } + + memset(&response, 0, sizeof(response)); + response.type = MSG_JOB_ASSIGNED; + response.data.assignment.job_id = worker->job_info.jobid; + response.data.assignment.ntasks = worker->ntasks; + strncpy(response.data.assignment.cmd, + worker->job_cmd, + sizeof(response.data.assignment.cmd)); + if (worker->job_exdata) { + strncpy(response.data.assignment.exdata, + worker->job_exdata, + sizeof(response.data.assignment.exdata)); + } else { + response.data.assignment.exdata[0] = '\0'; + } + + if (worker->q_pre_exec) { + strncpy(response.data.assignment.q_pre_exec, + worker->q_pre_exec, + sizeof(response.data.assignment.q_pre_exec)); + } else { + response.data.assignment.q_pre_exec[0] = '\0'; + } + + if (worker->q_post_exec) { + strncpy(response.data.assignment.q_post_exec, + worker->q_post_exec, + sizeof(response.data.assignment.q_post_exec)); + } else { + response.data.assignment.q_post_exec[0] = '\0'; + } + + if (worker->job_pre_exec) { + strncpy(response.data.assignment.job_pre_exec, + worker->job_pre_exec, + sizeof(response.data.assignment.job_pre_exec)); + } else { + response.data.assignment.job_pre_exec[0] = '\0'; + } + + if (worker->job_post_exec) { + strncpy(response.data.assignment.job_post_exec, + worker->job_post_exec, + sizeof(response.data.assignment.job_post_exec)); + } else { + response.data.assignment.job_post_exec[0] = '\0'; + } + + if (requesting_client) { + add_job_client_mapping(worker->job_info.jobid, requesting_client); + } + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t send_buf = uv_buf_init((char*)&response, sizeof(response)); + uv_write(req, client, &send_buf, 1, on_write_complete); + } + } + else if (msg->type == MSG_JOB_STATUS || + msg->type == MSG_JOB_COMPLETED) { + if (master_status_callback) { + master_status_callback(&msg->data.status); + } + } + else if (msg->type == MSG_JOB_SIGNAL_ACK) { + XCECD_LOG(XCE_LOG_DEBUG, + "Received signal acknowledgment for job %lu, signal %d, force %d", + msg->data.signal.job_id, + msg->data.signal.signal, + msg->data.signal.force); + } + else if (msg->type == MSG_JOB_CKPT_ACK) { + XCECD_LOG(XCE_LOG_DEBUG, + "Received checkpoint acknowledgment for job %lu, signal %d, method %s", + msg->data.ckpt.job_id, + msg->data.ckpt.kill, + msg->data.ckpt.method); + } + else if (msg->type == MSG_JOB_CKPT_RESULT) { + XCECD_LOG(XCE_LOG_DEBUG, + "Received checkpoint result for job %lu", + msg->data.ckpt.job_id); + if (master_ckpt_result_callback) { + master_ckpt_result_callback(msg->data.ckpt_result.job_id, + msg->data.ckpt_result.status, + msg->data.ckpt_result.restore_cmd); + } + } + else if (msg->type == MSG_REMOTE_START_JOB) { + XCECD_LOG(XCE_LOG_DEBUG, + "Received remote start request for host '%s'", + msg->data.remote_start.hostname); + + int rc = xcecd_job_remote_start(msg->data.remote_start.jobid, + msg->data.remote_start.hostname, + msg->data.remote_start.command); + + ipc_message_t response; + memset(&response, 0, sizeof(response)); + response.type = MSG_REMOTE_START_JOB_ACK; + if (rc == 0) { + response.data.remote_start_ack.success = 1; + } else { + response.data.remote_start_ack.success = 0; + snprintf(response.data.remote_start_ack.error_message, + sizeof(response.data.remote_start_ack.error_message), + "Failed to start remote job with rc=%d", rc); + } + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t send_buf = uv_buf_init((char*)&response, sizeof(response)); + uv_write(req, client, &send_buf, 1, on_write_complete); + } + } + else if (msg->type == MSG_ERROR) { + XCECD_LOG(XCE_LOG_ERROR, + "Error message from client: %s", + msg->data.error_message); + } + } + + if (buf->base) { + free(buf->base); + } +#endif +} + +/** + * @brief on worker read (callback) + * @param server + * @param nread + * @param buf + * @return void + */ +static void on_worker_read(uv_stream_t* server, + ssize_t nread, + const uv_buf_t* buf) +{ + if (nread < 0) { + if (nread != UV_EOF) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Read error: %s", + uv_strerror(nread)); + } + worker_connected = false; + uv_close((uv_handle_t*)&worker_pipe, NULL); + if (buf->base) { + free(buf->base); + } + return; + } + + if (nread == 0) { + if (buf->base) { + free(buf->base); + } + return; + } + + if (nread >= sizeof(ipc_message_t)) { + ipc_message_t* msg = (ipc_message_t*)buf->base; + + if (msg->type == MSG_JOB_ASSIGNED && + worker_job_callback) { + worker_id = msg->data.assignment.job_id; + worker_job_callback(&msg->data.assignment); + } + else if (msg->type == MSG_JOB_SIGNAL && + worker_signal_callback) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Received signal %d for job %lu", + msg->data.signal.signal, + msg->data.signal.job_id); + + worker_signal_callback(msg->data.signal.job_id, + msg->data.signal.signal, + msg->data.signal.force); + + ipc_worker_send_signal_ack(msg->data.signal.job_id, + msg->data.signal.signal, + msg->data.signal.force); + } + else if (msg->type == MSG_JOB_CKPT && + worker_ckpt_callback) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Received checkpoint request for job %lu", + msg->data.ckpt.job_id); + + worker_ckpt_callback(msg->data.ckpt.job_id, + msg->data.ckpt.kill, + msg->data.ckpt.method); + } + else if (msg->type == MSG_ERROR) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error message from master: %s", + msg->data.error_message); + } + } + + if (buf->base) { + free(buf->base); + } +} + +/** + * @brief request connection (callback) + * @param server + * @param status + */ +static void on_connection(uv_stream_t* server, + int status) +{ + if (status < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Connection error: %s\n", + uv_strerror(status)); + return; + } + + uv_pipe_t* client = (uv_pipe_t*)calloc(1, sizeof(uv_pipe_t)); + if (!client) { + XCECD_LOG(XCE_LOG_ERROR, + "Memory allocation failed\n"); + return; + } + + uv_pipe_init(loop, client, 0); + + if (uv_accept(server, (uv_stream_t*)client) == 0) { + client_t* existing_client = find_client(client); + if (existing_client) { + XCECD_LOG(XCE_LOG_DEBUG, + "Closing existing connection for this worker."); + remove_client((uv_pipe_t*)existing_client->pipe); + uv_close((uv_handle_t*)existing_client->pipe, + on_handle_close); + } + + add_client(client); + uv_read_start((uv_stream_t*)client, + alloc_buffer, + on_master_read); + + XCECD_LOG(XCE_LOG_DEBUG, + "Accepted new connection from client."); + } else { + uv_close((uv_handle_t*)client, on_handle_close); + } +} + +/** + * @brief connection result (callback) + * @param req + * @param status + */ +static void on_connect(uv_connect_t* req, + int status) +{ + if (status < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Connect error: %s\n", + uv_strerror(status)); + + worker_connected = false; + + if (worker_connection_callback) { + worker_connection_callback(false); + } + + if (req) { + free(req); + } + + return; + } + + worker_connected = true; + if (worker_connection_callback) { + worker_connection_callback(true); + } + + uv_read_start((uv_stream_t*)&worker_pipe, + alloc_buffer, + on_worker_read); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Connected to master at %s", + socket_path); + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Active handles: %d", + uv_loop_alive(loop)); + + if (req) { + free(req); + } +} + +/** + * @brief reconnection result (callback) + * @param req + * @param status + */ +static void on_reconnect(uv_connect_t* req, + int status) +{ + if (status < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Reconnect error: %s\n", + uv_strerror(status)); + worker_connected = false; + + if (worker_reconnect_callback) { + worker_reconnect_callback(false); + } + + if (req) { + free(req); + } + + return; + } + + worker_connected = true; + if (worker_reconnect_callback) { + worker_reconnect_callback(true); + } + + uv_read_start((uv_stream_t*)&worker_pipe, + alloc_buffer, + on_worker_read); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Reconnected to master at %s", + socket_path); + + if (req) { + free(req); + } +} + + +/** + * @brief count all open handles + * @param handle + * @param data + * @return void + */ +static void count_open_handles(uv_handle_t* handle, + void* data) +{ + int* open_handles = (int*)data; + if (!uv_is_closing(handle)) { + (*open_handles)++; + } +} + +/** + * @brief close all handles + * @param handle + * @param arg + */ +static void close_all_handles(uv_handle_t* handle, + void* arg) +{ + if (!uv_is_closing(handle)) { + //uv_handle_type type = handle->type; + //void* data = arg; + + uv_close(handle, on_handle_close); + } +} + +/** + * @brief worker init + * @param path - socket path + * @return bool + */ +bool ipc_worker_init(const char* path) { + if (!path) return false; + + memset(socket_path, 0, sizeof(socket_path)); + strncpy(socket_path, path, sizeof(socket_path) - 1); + + loop = uv_loop_new(); + if (!loop) return false; + + uv_pipe_init(loop, &worker_pipe, 0); + + return true; +} + +/** + * @brief worker connect + * @param callback + * @return void + */ +void ipc_worker_connect(connection_callback_t callback) { + if (callback) { + worker_connection_callback = callback; + } + + uv_connect_t* connect_req = (uv_connect_t*)calloc(1, sizeof(uv_connect_t)); + if (!connect_req) { + if (callback){ + callback(false); + } + return; + } + + uv_pipe_connect(connect_req, + &worker_pipe, + socket_path, + on_connect); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Connecting to master at %s...", + socket_path); +} + + +/** + * @brief Disconnect and reconnect the worker to the master. + * This function is called when the worker needs to re-establish + * @param callback + */ +void ipc_worker_reconnect(worker_reconnect_callback_t callback) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Attempting to reconnect to master..."); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Closing existing worker pipe..."); + uv_read_stop((uv_stream_t*)&worker_pipe); + + if (uv_is_active((uv_handle_t*)&worker_pipe)) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Closing active worker pipe..."); + uv_close((uv_handle_t*)&worker_pipe, NULL); + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Reinitializing worker pipe..."); + uv_pipe_init(loop, &worker_pipe, 0); + + uv_connect_t* connect_req = (uv_connect_t*)calloc(1, sizeof(uv_connect_t)); + if (!connect_req) { + if (callback){ + callback(false); + } + return; + } + + uv_pipe_connect(connect_req, + &worker_pipe, + socket_path, + on_reconnect); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Re-connecting to master at %s...", + socket_path); +} + +/** + * @brief worker run + * @param mode + * @return void + */ +void ipc_worker_run(uv_run_mode mode) { + if (loop) { + uv_run(loop, mode); + } +} + +/** + * @brief worker stop + * @return void + */ +void ipc_worker_stop() { + if (!loop) return; + + if (worker_connected) { + uv_read_stop((uv_stream_t*)&worker_pipe); + worker_connected = false; + } + + if (!uv_is_closing((uv_handle_t*)&worker_pipe)) { + uv_close((uv_handle_t*)&worker_pipe, NULL); + } + + uv_run(loop, UV_RUN_ONCE); + + uv_stop(loop); + + worker_job_callback = NULL; + worker_connection_callback = NULL; + worker_reconnect_callback = NULL; + + + /* We do not close or stop event loop as the child will exit + * immediately and the OS will take care of all resources + */ + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Worker event loop stopped."); + + loop = NULL; +} + +/** + * @brief worker request job + * @return void + */ +void ipc_worker_request_job() { + if (!worker_connected && !job_requested) return; + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_GET_JOB; + + msg.worker_pid = getppid(); + if (msg.worker_pid == 1) { + // If the worker is a child of init, use the current process ID + msg.worker_pid = getpid(); + } + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + uv_write(req, + (uv_stream_t*)&worker_pipe, + &buf, + 1, + on_write_complete); + } + + job_requested = true; + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Requesting job from master..."); +} + +/** + * @brief worker send status + * @param job_info + * @param status_message + * @return void + */ +void ipc_worker_send_status(job_runtime_info_t* job_info, + const char* status_message) +{ + if (!worker_connected) return; + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_JOB_STATUS; + msg.data.status.job_id = job_info->job_id; + msg.data.status.job_pid = job_info->job_pid; + msg.data.status.status_code = job_info->job_status; + msg.data.status.worker_pid = job_info->worker_pid; + msg.data.status.exit_code = job_info->exit_code; + msg.data.status.worker_status = job_info->worker_status; + msg.data.status.stage = job_info->stage; + msg.data.status.post_done = job_info->post_done; + + strncpy(msg.data.status.start_exdata, + job_info->exdata, + sizeof(msg.data.status.start_exdata) - 1); + msg.data.status.start_exdata[sizeof(msg.data.status.start_exdata) - 1] = '\0'; + + if (status_message) { + strncpy(msg.data.status.status_message, + status_message, + sizeof(msg.data.status.status_message) - 1); + msg.data.status.status_message[sizeof(msg.data.status.status_message) - 1] = '\0'; + } else { + strncpy(msg.data.status.status_message, + "No message", + sizeof(msg.data.status.status_message) - 1); + } + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + int r = uv_write(req, + (uv_stream_t*)&worker_pipe, + &buf, + 1, + on_write_complete); + if (r < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to send job status: %s", + uv_strerror(r)); + free(req); + return; + } + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Sending job status to master: %s", + status_message); + + if (job_info->job_status == JOB_DONE) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Job %lu completed successfully.", + job_info->job_id); + } else if (job_info->job_status == JOB_FAILED) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Job %lu failed: %s", + job_info->job_id, + status_message); + } + else if (job_info->job_status == JOB_EXITED) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Job %lu exited with code %d: %s", + job_info->job_id, + job_info->exit_code, + status_message); + } +} + +void ipc_worker_job_completed(uint64_t job_id) { + if (!worker_connected) return; + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_JOB_COMPLETED; + msg.data.status.job_id = job_id; + msg.data.status.status_code = 0; + strncpy(msg.data.status.status_message, + "Completed", + sizeof(msg.data.status.status_message) - 1); + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + uv_write(req, + (uv_stream_t*)&worker_pipe, + &buf, + 1, + on_write_complete); + } +} + +/** + * @brief master init + * @param path - socket path + * @return bool + */ +bool ipc_master_init(const char* path) { + if (!path) return false; + + memset(socket_path, 0, sizeof(socket_path)); + strncpy(socket_path, path, sizeof(socket_path) - 1); + + loop = uv_loop_new(); + if (!loop) return false; + + srand((unsigned int)time(NULL) ^ getpid()); + + uv_pipe_init(loop, &master_pipe, 0); + + exit_timer = (uv_timer_t*)calloc(1, sizeof(uv_timer_t)); + if (!exit_timer) { + uv_loop_delete(loop); + loop = NULL; + return false; + } + + uv_timer_init(loop, exit_timer); + /* check exit condition every 500ms */ + uv_timer_start(exit_timer, + check_exit_condition, + 500, + 500); + + unlink(socket_path); + + int r = uv_pipe_bind(&master_pipe, socket_path); + if (r) { + XCECD_LOG(XCE_LOG_ERROR, + "Bind error: %s\n", + uv_strerror(r)); + free(exit_timer); + exit_timer = NULL; + uv_loop_delete(loop); + loop = NULL; + return false; + } + + r = uv_listen((uv_stream_t*)&master_pipe, + 128, + on_connection); + + if (r) { + XCECD_LOG(XCE_LOG_ERROR, + "Listen error: %s\n", + uv_strerror(r)); + free(exit_timer); + exit_timer = NULL; + uv_loop_delete(loop); + loop = NULL; + return false; + } + + return true; +} + +void ipc_master_run(uv_run_mode mode) { + if (loop) { + uv_run(loop, mode); + } +} + +/** + * @brief master stop + * @return void + */ +void ipc_master_stop() { + if (!loop) return; + + XCECD_LOG(XCE_LOG_INFO, + "Stopping event loop..."); + + job_client_map_t* current_mapping = job_client_map; + job_client_map_t* next_mapping; + + while (current_mapping) { + next_mapping = current_mapping->next; + free(current_mapping); + current_mapping = next_mapping; + } + job_client_map = NULL; + + if (exit_timer && + !uv_is_closing((uv_handle_t*)exit_timer)) { + uv_timer_stop(exit_timer); + uv_close((uv_handle_t*)exit_timer, + on_handle_close); + } + + uv_walk(loop, + close_all_handles, + NULL); + + uv_run(loop, UV_RUN_NOWAIT); + + int attempts = 20; + while (attempts--) { + int open_handles = 0; + uv_walk(loop, + count_open_handles, + &open_handles); + + if (open_handles == 0) { + break; + } + + uv_run(loop, UV_RUN_NOWAIT); + + if (open_handles > 0 && attempts > 0) { + usleep(20000); + } + } + + client_t* current = clients; + client_t* next; + + while (current) { + next = current->next; + free(current); + current = next; + } + clients = NULL; + + + /* Do not use uv_loop_close and uv_loop_delete here. They will + * cause assertion failures. We only free our own allocated resources. + */ + XCECD_LOG(XCE_LOG_INFO, + "Main event loop stopped."); + + loop = NULL; +} + +/** + * @brief get ipc event loop + * @return uv_loop_t* + */ +uv_loop_t* ipc_get_event_loop() { + return loop; +} + +void ipc_master_set_status_callback(job_status_callback_t callback) { + master_status_callback = callback; +} + +void ipc_master_set_disconnect_callback(client_disconnect_callback_t callback) { + master_disconnect_callback = callback; +} + +void ipc_master_set_heartbeat_callback(heartbeat_callback_t callback) { + master_heartbeat_callback = callback; +} + +/** + * @brief worker set job callback + * @param callback + * @return void + */ +void ipc_worker_set_job_callback(job_callback_t callback) { + worker_job_callback = callback; +} + +/** + * @brief worker set connection check callback + * @param callback + * @return void + */ +void ipc_worker_set_reconnection_callback(worker_reconnect_callback_t callback) { + worker_reconnect_callback = callback; +} + +/** + * @brief master send job signal to worker + * @param job_id - job ID + * @param signal - signal to send + * @param force - force send signal + * @return bool - true if sent successfully + */ +bool ipc_master_send_job_signal(uint64_t job_id, + int signal, + bool force) +{ + client_t* target_client = find_client_by_job_id(job_id); + + if (!target_client) { + XCECD_LOG(XCE_LOG_ERROR, + "No worker found for job %lu", + job_id); + return false; + } + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_JOB_SIGNAL; + msg.data.signal.job_id = job_id; + msg.data.signal.signal = signal; + msg.data.signal.force = force; + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + int r = uv_write(req, + (uv_stream_t*)target_client->pipe, + &buf, + 1, + on_write_complete); + if (r == 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "Sent signal %d for job %lu to worker %d", + signal, job_id, target_client->worker_pid); + return true; + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to send signal %d for job %lu: %s", + signal, job_id, uv_strerror(r)); + free(req); + return false; + } + } + + return false; +} + +/** + * @brief worker send signal acknowledgment + * @param job_id - job ID + * @param signal - signal that was processed + * @param force - whether the signal was forced + * @return void + */ +void ipc_worker_send_signal_ack(uint64_t job_id, + int signal, + bool force) +{ + if (!worker_connected) return; + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_JOB_SIGNAL_ACK; + msg.data.signal.job_id = job_id; + msg.data.signal.signal = signal; + msg.data.signal.force = force; + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + int r = uv_write(req, + (uv_stream_t*)&worker_pipe, + &buf, + 1, + on_write_complete); + if (r < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to send signal acknowledgment: %s", + uv_strerror(r)); + free(req); + return; + } + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Sent signal acknowledgment for job %lu, signal %d, force %d", + job_id, signal, force); +} + +/** + * @brief worker set signal callback + * @param callback + * @return void + */ +void ipc_worker_set_signal_callback(job_signal_callback_t callback) { + worker_signal_callback = callback; +} + +/** + * @brief worker set checkpoint callback + * @param callback + * @return void + */ +void ipc_worker_set_ckpt_callback(job_ckpt_callback_t callback) { + worker_ckpt_callback = callback; +} + +/** + * @brief master send job checkpoint to worker + * @param job_id - job ID + * @param kill - whether to kill the job after checkpoint + * @param method - checkpoint method + * @return bool - true if sent successfully + */ +bool ipc_master_send_job_ckpt(uint64_t job_id, + int kill, + const char* method) +{ + client_t* target_client = find_client_by_job_id(job_id); + + if (!target_client) { + XCECD_LOG(XCE_LOG_ERROR, + "No worker found for job %lu", + job_id); + return false; + } + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_JOB_CKPT; + msg.data.ckpt.job_id = job_id; + msg.data.ckpt.kill = kill; + strncpy(msg.data.ckpt.method, method, sizeof(msg.data.ckpt.method) - 1); + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + int r = uv_write(req, + (uv_stream_t*)target_client->pipe, + &buf, + 1, + on_write_complete); + if (r == 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "Sent checkpoint request for job %lu to worker %d", + job_id, target_client->worker_pid); + return true; + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to send checkpoint request for job %lu: %s", + job_id, uv_strerror(r)); + free(req); + return false; + } + } + + return false; +} + +/** + * @brief worker send checkpoint acknowledgment + * @param job_id - job ID + * @param kill - whether checkpointing successful or not (0 success,) + * @param restore_cmd - command to restore the job + * @return bool - true if sent successfully + */ +bool ipc_worker_send_ckpt_ack(uint64_t job_id, + int kill, + const char* method) +{ + if (!worker_connected) return false; + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_JOB_CKPT; + msg.data.ckpt.job_id = job_id; + msg.data.ckpt.kill = kill; + strncpy(msg.data.ckpt.method, method, sizeof(msg.data.ckpt.method) - 1); + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + int r = uv_write(req, + (uv_stream_t*)&worker_pipe, + &buf, + 1, + on_write_complete); + if (r < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to send job checkpoint: %s", + uv_strerror(r)); + free(req); + return false; + } + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Sent job checkpoint for job %lu", + job_id); + + return true; +} + +/** + * @brief master set checkpoint result callback + * @param callback + * @return void + */ +void ipc_master_set_ckpt_callback(job_ckpt_callback_t callback) { + master_ckpt_result_callback = callback; +} + +/** + * + */ +bool ipc_worker_send_ckpt_result(uint64_t job_id, + int status, + const char* restore_cmd) +{ + if (!worker_connected) return false; + + ipc_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_JOB_CKPT_RESULT; + msg.data.ckpt_result.job_id = job_id; + msg.data.ckpt_result.status = status; + strncpy(msg.data.ckpt_result.restore_cmd, restore_cmd, sizeof(msg.data.ckpt_result.restore_cmd) - 1); + + uv_write_t* req = (uv_write_t*)calloc(1, sizeof(uv_write_t)); + if (req) { + uv_buf_t buf = uv_buf_init((char*)&msg, sizeof(msg)); + int r = uv_write(req, + (uv_stream_t*)&worker_pipe, + &buf, + 1, + on_write_complete); + if (r < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to send job checkpoint result: %s", + uv_strerror(r)); + free(req); + return false; + } + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Sent job checkpoint result for job %lu", + job_id); + + return true; +} diff --git a/src/worker/src/uv_proc.c b/src/worker/src/uv_proc.c new file mode 100755 index 00000000..af5cdb87 --- /dev/null +++ b/src/worker/src/uv_proc.c @@ -0,0 +1,520 @@ +#include "uv_proc.h" +#include "xcelog.h" +#include +#include +#include +#include +#include +#include +#include + + +static void on_process_exit(uv_process_t* process, + int64_t exit_status, + int term_signal) +{ + uv_proc_manager_t* manager = (uv_proc_manager_t*)process->data; + + if (manager == NULL) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Process exit callback: unable to get manager pointer"); + return; + } + + // save exit status and signal + manager->last_exit_code = (int)exit_status; + manager->last_term_signal = term_signal; + + int i; + for (i = 0; i < manager->proc_count; i++) { + if (manager->proc_list[i] == process) { + manager->proc_list[i] = NULL; + break; + } + } + + manager->remaining_procs--; + + if (manager->remaining_procs == 0) { + if (term_signal) { + manager->status = UV_PROC_TERMINATED; + } else { + manager->status = exit_status == 0 ? UV_PROC_COMPLETED : UV_PROC_ERROR; + } + + int j, k = 0; + for (j = 0; j < manager->proc_count; j++) { + if (manager->proc_list[j] != NULL) { + manager->proc_list[k++] = manager->proc_list[j]; + } + } + manager->proc_count = k; + + if (manager->exit_cb) { + manager->exit_cb(process->pid, + (int)exit_status, + term_signal, + manager->user_data); + } + } + + uv_close((uv_handle_t*)process, NULL); + free(process); + + if (manager->active_proc == process) { + manager->active_proc = NULL; + } +} + +int uv_proc_init(uv_proc_manager_t* manager, + uv_loop_t* loop) +{ + if (manager == NULL || loop == NULL) { + return UV_EINVAL; + } + + memset(manager, 0, sizeof(uv_proc_manager_t)); + manager->job_id = 0; + manager->loop = loop; + manager->status = UV_PROC_IDLE; + manager->last_exit_code = 0; + manager->last_term_signal = 0; + manager->active_proc = NULL; + manager->user_data = NULL; + manager->exit_cb = NULL; + + manager->proc_list = NULL; + manager->proc_count = 0; + manager->proc_capacity = 0; + manager->remaining_procs = 0; + + return 0; +} + +static pid_t start_background_service(const char* command) +{ + pid_t pid; + int status; + + pid = fork(); + + if (pid < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "fork failed: %s", + strerror(errno)); + return -1; + } else if (pid == 0) { + + // 1. create new session to detach from terminal + if (setsid() < 0) { + exit(EXIT_FAILURE); + } + + // 2. double fork to ensure the process is not a session leader + pid_t child_pid = fork(); + if (child_pid < 0) { + exit(EXIT_FAILURE); + } else if (child_pid > 0) { + // first child exits, leaving the second child as the session leader + exit(EXIT_SUCCESS); + } + + // 3. change working directory to root + chdir("/"); + + // 4. redirect standard input, output, and error to /dev/null + + freopen("/dev/null", "r", stdin); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + + // 5. execute the command + char* args[] = {"/bin/sh", "-c", (char*)command, NULL}; + execvp("/bin/sh", args); + + exit(EXIT_FAILURE); + } else { + if (waitpid(pid, &status, 0) < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "waitpid failed: %s", + strerror(errno)); + return -1; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + // return the PID of the background service + return pid; + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "first child process failed"); + return -1; + } + } +} + +static int execute_blocking_command(const char* command, + char* const args[]) +{ + pid_t pid; + int status; + + pid = fork(); + + if (pid < 0) { + // fork failed + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "fork failed: %s", + strerror(errno)); + return -1; + } else if (pid == 0) { + // child process + char* myargs[] = {"/bin/sh", "-c", (char*)command, NULL}; + execvp("/bin/sh", myargs); + + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "execvp failed: %s", + strerror(errno)); + exit(EXIT_FAILURE); + } else { + // parent process + if (waitpid(pid, &status, 0) < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "waitpid failed: %s", + strerror(errno)); + return -1; + } + + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "command terminated by %d signal", + WTERMSIG(status)); + return -1; + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "unknown command exit status"); + return -1; + } + } +} + +int uv_proc_pre_exec(uv_proc_manager_t* manager, + const char* command, + char* const args[]) +{ + if (manager == NULL || command == NULL) { + return -1; + } + + int result = execute_blocking_command(command, args); + + return result; +} + +int uv_proc_exec(uv_proc_manager_t* manager, + const char* command, + char* const args[], + uv_proc_exit_cb exit_cb, + void* user_data, + int ntasks) +{ + if (manager == NULL || command == NULL) { + return UV_EINVAL; + } + + if (ntasks < 1) { + ntasks = 1; + } + + if (manager->status == UV_PROC_RUNNING) { + return UV_EBUSY; + } + + manager->exit_cb = exit_cb; + manager->user_data = user_data; + + if (manager->proc_list == NULL || manager->proc_capacity < ntasks) { + uv_process_t** new_list = (uv_process_t**)realloc( + manager->proc_list, ntasks * sizeof(uv_process_t*)); + if (new_list == NULL) { + return UV_ENOMEM; + } + manager->proc_list = new_list; + manager->proc_capacity = ntasks; + } + + manager->proc_count = 0; + manager->remaining_procs = ntasks; + + for (int i = 0; i < ntasks; i++) { + uv_process_t* process = (uv_process_t*)calloc(1, sizeof(uv_process_t)); + if (process == NULL) { + for (int j = 0; j < manager->proc_count; j++) { + uv_process_kill(manager->proc_list[j], SIGTERM); + free(manager->proc_list[j]); + } + manager->proc_count = 0; + manager->remaining_procs = 0; + return UV_ENOMEM; + } + + char job_output[256]; + snprintf(job_output, sizeof(job_output), + "/tmp/job_%lu_task%d.out", + manager->job_id, i); + + uv_stdio_container_t stdio[3]; + uv_fs_t file_req; + int fd = uv_fs_open(manager->loop, + &file_req, + job_output, + O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR, + NULL); + if (fd < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "uv_fs_open failed: %s", + uv_strerror(fd)); + free(process); + + for (int j = 0; j < manager->proc_count; j++) { + uv_process_kill(manager->proc_list[j], SIGTERM); + free(manager->proc_list[j]); + } + manager->proc_count = 0; + manager->remaining_procs = 0; + return fd; + } + + stdio[0].flags = UV_IGNORE; + stdio[1].flags = UV_INHERIT_FD; + stdio[1].data.fd = fd; + stdio[2].flags = UV_INHERIT_FD; + stdio[2].data.fd = fd; + + const char* myargs[4]; + myargs[0] = "/bin/sh"; + myargs[1] = "-c"; + myargs[2] = command; + myargs[3] = NULL; + + uv_process_options_t options = {0}; + options.exit_cb = on_process_exit; + options.file = myargs[0]; + options.args = (char**)myargs; + options.env = NULL; + options.cwd = NULL; + options.stdio_count = 3; + options.stdio = stdio; + options.flags = UV_PROCESS_DETACHED; + + int result = uv_spawn(manager->loop, process, &options); + + uv_fs_close(manager->loop, &file_req, fd, NULL); + uv_fs_req_cleanup(&file_req); + + if (result != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "uv_spawn failed: %s", + uv_strerror(result)); + free(process); + + for (int j = 0; j < manager->proc_count; j++) { + uv_process_kill(manager->proc_list[j], SIGTERM); + free(manager->proc_list[j]); + } + manager->proc_count = 0; + manager->remaining_procs = 0; + return result; + } + + process->data = manager; + manager->proc_list[manager->proc_count++] = process; + } + + if (manager->proc_count > 0) { + manager->active_proc = manager->proc_list[0]; // set the first process as active + manager->status = UV_PROC_RUNNING; + return 0; + } + + return UV_UNKNOWN; +} + +int uv_proc_post_exec(uv_proc_manager_t* manager, + const char* command, + char* const args[]) +{ + if (manager == NULL || command == NULL) { + return -1; + } + + int result = execute_blocking_command(command, args); + + return result; +} + +int uv_proc_kill(uv_proc_manager_t* manager, + int signum) +{ + if (manager == NULL) { + return UV_EINVAL; + } + + int result = 0; + + for (int i = 0; i < manager->proc_count; i++) { + if (manager->proc_list[i] != NULL) { + int ret = uv_process_kill(manager->proc_list[i], signum); + if (ret != 0 && result == 0) { + result = ret; + } + } + } + + return result; +} + +uv_proc_status_t uv_proc_get_status(uv_proc_manager_t* manager) { + if (manager == NULL) { + return UV_PROC_UNKNOWN; + } + + return manager->status; +} + +int uv_proc_get_pid(uv_proc_manager_t* manager) { + if (manager == NULL || manager->active_proc == NULL) { + return -1; + } + + return manager->active_proc->pid; +} + +int uv_proc_get_exit_code(uv_proc_manager_t* manager) { + if (manager == NULL) { + return -1; + } + + return manager->last_exit_code; +} + +int uv_proc_get_term_signal(uv_proc_manager_t* manager) { + if (manager == NULL) { + return -1; + } + + return manager->last_term_signal; +} + +void uv_proc_cleanup(uv_proc_manager_t* manager) { + if (manager == NULL) { + return; + } + + for (int i = 0; i < manager->proc_count; i++) { + if (manager->proc_list[i] != NULL) { + uv_process_kill(manager->proc_list[i], SIGTERM); + } + } + + if (manager->proc_list != NULL) { + free(manager->proc_list); + manager->proc_list = NULL; + } + + manager->proc_count = 0; + manager->proc_capacity = 0; + manager->remaining_procs = 0; + manager->active_proc = NULL; + manager->status = UV_PROC_IDLE; + manager->exit_cb = NULL; + manager->user_data = NULL; +} + +int uv_proc_wait(uv_proc_manager_t* manager, + int timeout_ms) +{ + if (manager == NULL || + manager->active_proc == NULL) { + return UV_EINVAL; + } + + pid_t pid = manager->active_proc->pid; + int status; + + if (timeout_ms <= 0) { + // wait indefinitely + if (waitpid(pid, &status, 0) < 0) { + return UV_ESRCH; + } + } else { + // timeout waiting + time_t start_time = time(NULL); + int elapsed_ms = 0; + int result; + + // wait for the process to exit with timeout + while (elapsed_ms < timeout_ms) { + result = waitpid(pid, &status, WNOHANG); + + if (result > 0) { + // process exited + break; + } else if (result == 0) { + // process still running + usleep(10 * 1000); + elapsed_ms = (int)((time(NULL) - start_time) * 1000); + continue; + } else { + // error occurred + return UV_ESRCH; + } + } + + if (elapsed_ms >= timeout_ms) { + return UV_ETIMEDOUT; + } + } + + // update process status + if (WIFEXITED(status)) { + manager->last_exit_code = WEXITSTATUS(status); + manager->last_term_signal = 0; + manager->status = manager->last_exit_code == 0 ? UV_PROC_COMPLETED : UV_PROC_ERROR; + } else if (WIFSIGNALED(status)) { + manager->last_exit_code = -1; + manager->last_term_signal = WTERMSIG(status); + manager->status = UV_PROC_TERMINATED; + } else { + return UV_UNKNOWN; + } + + return 0; +} + +int uv_proc_pre_exec_background(uv_proc_manager_t* manager, + const char* command, + char* const args[], + int background) +{ + if (manager == NULL || command == NULL) { + return -1; + } + + if (background) { + return start_background_service(command); + } else { + return execute_blocking_command(command, args); + } +} + +int uv_proc_exec_single(uv_proc_manager_t* manager, + const char* command, + char* const args[], + uv_proc_exit_cb exit_cb, + void* user_data) +{ + return uv_proc_exec(manager, command, args, exit_cb, user_data, 1); +} \ No newline at end of file diff --git a/src/worker/src/xcecd.c b/src/worker/src/xcecd.c new file mode 100644 index 00000000..a4541428 --- /dev/null +++ b/src/worker/src/xcecd.c @@ -0,0 +1,789 @@ +#include "xcecd.h" +#include "xcecd_cgroups.h" +#include "xcecd_utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +xcecd_context_t xcecd_ctx = {0}; +hostattrs_t host_attrs = {0}; + + +/** + * fake pids used in virtual cluster + * ranges: 1000 - 65534 + */ +#ifdef VIRTUAL_CLUSTER +static int virtual_pid = 1000; +#endif + + +/** + * @brief start xcecd_worker proecess and send command to it + */ +void xcecd_process_command(void* arg) { + pid_t pid = 0; + char unit_name[256]; + + fork_task_t* ctx = (fork_task_t*)arg; + if (!ctx || !ctx->startinfo || !ctx->startinfo->cmdline) { + XCECD_LOG(XCE_LOG_ERROR, + "Invalid task context, command: %s", + ctx->startinfo ? ctx->startinfo->cmdline : "NULL"); + + return; + } + +#ifndef DBUS_MANAGER + if (!ctx->manager) { + XCECD_LOG(XCE_LOG_ERROR, + "Invalid process manager"); + return; + } +#endif + + XCECD_LOG(XCE_LOG_DEBUG, + "Starting worker as user %s to process command: %s.", + ctx->startinfo->execuser ? ctx->startinfo->execuser : "nobody", + ctx->startinfo->cmdline); + + if (!ctx->startinfo) { + xcecd_job_start_error(ctx->xce_jobid); + XCECD_LOG(XCE_LOG_FATAL, + "No startinfo provided, job %lu start failed.", + ctx->xce_jobid); + + return; + } + +#ifndef DBUS_MANAGER + if (process_manager_start_worker(ctx->manager) != 0) { + XCECD_LOG(XCE_LOG_FATAL, + "Failed to start worker process"); + + xcecd_job_start_error(ctx->xce_jobid); + return; + } + + /* adding worker to worker list,and update worker pid */ + int pid = get_current_process_pid(ctx->manager); + if (pid != -1) { + + add_worker(&xcecd_ctx.worker_list, + pid, + WORKER_RUNNING, + ctx->xce_jobid, + ctx->ntasks, + ctx->startinfo); + + if (!xcecd_db_insert_worker(xcecd_ctx.db, + pid, + WORKER_RUNNING, + ctx->xce_jobid, + JOB_RUNNING)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to insert worker into database, XCECD exit..."); + dbus_stop_all_workers(&xcecd_ctx.worker_list, + NULL); + xcecd_db_is_available(xcecd_ctx.db, + xcecd_ctx.db_path); + } + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to get worker pid"); + + xcecd_job_start_error(ctx->xce_jobid); + return; + } + /* send command to worker */ +#else + +#ifndef VIRTUAL_CLUSTER + + char worker_params[1024] = {0}; + snprintf(worker_params, + sizeof(worker_params), + "-l %s -d %d\n", + xcecd_ctx.log_dir, + xcecd_ctx.debug_level); + + /* start worker with resource limits */ + if (!dbus_start_worker(ctx->limits, ctx->xce_jobid, worker_params)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to start worker with resource limits"); + + xcecd_job_start_error(ctx->xce_jobid); + if (ctx->limits) { + FREEUP(ctx->limits->cpu_affinity); + FREEUP(ctx->limits->io_device_path); + FREEUP(ctx->limits->user_name); + FREEUP(ctx->limits); + } + if (ctx->startinfo) { + FREEUP(ctx->startinfo->cmdline); + FREEUP(ctx->startinfo->exdata); + FREEUP(ctx->startinfo->execuser); + FREEUP(ctx->startinfo->exechome); + FREEUP(ctx->startinfo->execcwd); + FREEUP(ctx->startinfo->q_pre_exec); + FREEUP(ctx->startinfo->q_post_exec); + FREEUP(ctx->startinfo->job_pre_exec); + FREEUP(ctx->startinfo->job_post_exec); + FREEUP(ctx->startinfo->launch_type); + FREEUP(ctx->startinfo); + } + + return; + } + + snprintf(unit_name, + sizeof(unit_name), + "xcecd-worker-%lu.service", + ctx->xce_jobid); + + /* get worker pid */ + pid = dbus_get_worker_pid(unit_name); +#else + /* in virtual cluster we use faked pid */ + if (virtual_pid > 65534) { + virtual_pid = 1000; + } + pid = virtual_pid++; +#endif + + if (pid < 0) { + /* retry to get worker pid */ + int retry = 0; + int retry_limit = 5; + + while (pid < 0 && retry < retry_limit) { + usleep(100 * 1000); + pid = dbus_get_worker_pid(unit_name); + retry++; + } + + if (pid < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to get worker pid"); + + xcecd_job_start_error(ctx->xce_jobid); + return; + } else { + XCECD_LOG(XCE_LOG_DEBUG, + "Got worker pid %d after %d retries", + pid, + retry); + add_worker(&xcecd_ctx.worker_list, + pid, + WORKER_RUNNING, + ctx->xce_jobid, + ctx->ntasks, + ctx->startinfo); + + if (!xcecd_db_insert_worker(xcecd_ctx.db, + pid, + WORKER_RUNNING, + ctx->xce_jobid, + JOB_RUNNING)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to insert worker into database, XCECD exit..."); + dbus_stop_all_workers(&xcecd_ctx.worker_list, + NULL); + xcecd_db_is_available(xcecd_ctx.db, + xcecd_ctx.db_path); + } + } + } else { + add_worker(&xcecd_ctx.worker_list, + pid, + WORKER_RUNNING, + ctx->xce_jobid, + ctx->ntasks, + ctx->startinfo); + + if (!xcecd_db_insert_worker(xcecd_ctx.db, + pid, + WORKER_RUNNING, + ctx->xce_jobid, + JOB_RUNNING)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to insert worker into database, XCECD exit..."); + dbus_stop_all_workers(&xcecd_ctx.worker_list, + NULL); + xcecd_db_is_available(xcecd_ctx.db, + xcecd_ctx.db_path); + } + } + + /* send command to worker */ +#endif + + if (!ipc_master_check_connection()) { + XCECD_LOG(XCE_LOG_FATAL, + "IPC connection is broken, exit."); + exit(1); + } + + /* worker will callback for job command after startup */ + if (ctx->startinfo) { + FREEUP(ctx->startinfo->cmdline); + FREEUP(ctx->startinfo->exdata); + FREEUP(ctx->startinfo->execuser); + FREEUP(ctx->startinfo->exechome); + FREEUP(ctx->startinfo->execcwd); + FREEUP(ctx->startinfo->q_pre_exec); + FREEUP(ctx->startinfo->q_post_exec); + FREEUP(ctx->startinfo->job_pre_exec); + FREEUP(ctx->startinfo->job_post_exec); + FREEUP(ctx->startinfo->launch_type); + FREEUP(ctx->startinfo); + } + if (ctx->limits) { + FREEUP(ctx->limits->cpu_affinity); + FREEUP(ctx->limits->io_device_path); + FREEUP(ctx->limits->user_name); + FREEUP(ctx->limits); + } +} + +/* handling network connection request from client directly - disabled */ +const char* handle_network_request(const char* request, + void* user_data) +{ + if (!request || + !xcecd_ctx.process_manager || + !xcecd_ctx.thread_pool) { + return NULL; + } + + fork_task_t* ctx = malloc(sizeof(fork_task_t)); + if (!ctx) { + return NULL; + } + + ctx->startinfo = calloc(1, sizeof(job_startinfo_t)); + if (!ctx->startinfo) { + FREEUP(ctx); + return NULL; + } +#ifndef DBUS_MANAGER + ctx->manager = (process_manager_t*)xcecd_ctx.process_manager; +#endif + ctx->func = xcecd_process_command; + + if (submit_fork_task(xcecd_ctx.thread_pool, ctx) != 0) { + FREEUP(ctx->startinfo->cmdline); + FREEUP(ctx->startinfo); + FREEUP(ctx); + return NULL; + } + + return strdup("Task submitted successfully\n"); +} + +// TODO: next phase task - send workload and host info to controller +int send_status_and_receive_tasks(void) { + // disabled currently + return 0; + + int sockfd; + struct sockaddr_in server_addr; + char buffer[BUFFER_SIZE]; + static int error_count = 0; + int error_log_interval = 10; + + // 创建套接字 + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to create socket"); + return -1; + } + + // 设置服务器地址 + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(xcecd_ctx.remote_port); + + // 打印远程主机名和端口以进行调试 + XCECD_LOG(XCE_LOG_DEBUG, + "Connecting to remote host: %s, port: %d", + xcecd_ctx.remote_host, + xcecd_ctx.remote_port); + + // 使用getaddrinfo解析主机名 + struct addrinfo hints, *res; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + int err = getaddrinfo(xcecd_ctx.remote_host, + NULL, + &hints, + &res); + if (err != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "getaddrinfo: %s", + gai_strerror(err)); + close(sockfd); + return -1; + } + + struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr; + server_addr.sin_addr = addr_in->sin_addr; + freeaddrinfo(res); + + // 打印解析后的IP地址 + char ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, + &(server_addr.sin_addr), + ip_str, + INET_ADDRSTRLEN); + XCECD_LOG(XCE_LOG_DEBUG, + "Resolved IP address: %s", + ip_str); + + // 连接到服务器 + if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + error_count++; + if ((error_count == 1) || + (error_count % error_log_interval == 0)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed connecting to remote host: %s (attempt %d)", + strerror(errno), + error_count); + } + close(sockfd); + return -1; + } + + // 准备状态信息 + int running_processes = get_process_count(xcecd_ctx.process_manager); + host_node_t* node = NULL; + resource_sync_read(xcecd_ctx.resource_sync, &node); + + int slots = get_cpu_usage(node->node_status.max_cpu_cores, + running_processes); + snprintf(buffer, BUFFER_SIZE, "{\"slots\":%d}", slots); + + // 发送状态信息 + if (send(sockfd, buffer, strlen(buffer), 0) < 0) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to send status"); + close(sockfd); + return -1; + } + + /* 接收任务请求 */ + ssize_t bytes_received; + while ((bytes_received = recv(sockfd, buffer, BUFFER_SIZE - 1, 0)) > 0) { + buffer[bytes_received] = '\0'; + XCECD_LOG(XCE_LOG_DEBUG, + "Received task: %s", + buffer); + + /* 创建任务上下文并添加到fork线程池队列 */ + fork_task_t* ctx = calloc(1, sizeof(fork_task_t)); + if (ctx) { + ctx->startinfo = calloc(1, sizeof(job_startinfo_t)); + if (!ctx->startinfo) { + FREEUP(ctx); + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for start_info"); + continue; + } + ctx->startinfo->cmdline = strdup(buffer); + if (!ctx->startinfo->cmdline) { + FREEUP(ctx->startinfo); + FREEUP(ctx); + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for command"); + continue; + } +#ifndef DBUS_MANAGER + ctx->manager = (void *)xcecd_ctx.process_manager; +#endif + ctx->func = xcecd_process_command; + + if (submit_fork_task(xcecd_ctx.thread_pool, ctx) != 0) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to add task to thread pool"); + FREEUP(ctx->startinfo->cmdline); + FREEUP(ctx->startinfo); + FREEUP(ctx); + } else { + XCECD_LOG(XCE_LOG_DEBUG, "Task added to thread pool queue"); + } + } + } + + close(sockfd); + return 0; +} + +static void connector_to_controller(void* arg) { + + while (xcecd_ctx.running && + cc_ctx->running) { + checking_connection(); + if (xcecd_ctx.running && + cc_ctx->running) { + send_status_and_receive_tasks(); + } else { + XCECD_LOG(XCE_LOG_ERROR, + "XCECD daemon is not running, exiting thread"); + xcecd_ctx.running = false; + cc_ctx->running = false; + break; + } + sleep(xcecd_ctx.update_interval); + } + + return; +} + +int xcecd_config_init(xcecd_config_t* config) { + if (!config) { + return -1; + } + + /* TODO: add configuration file parsing */ + return 0; +} + +bool xcecd_config_is_valid(const xcecd_config_t* config) { + if (!config) { + return false; + } + + if (!config->remote_host || + strlen(config->remote_host) == 0 || + config->remote_port <= 0 || + config->join_interval <= 0 || + config->update_interval <= 0 || + config->thread_pool_size <= 0 || + !config->acct_file || + !config->log_file || + !config->work_dir || + !config->domain_sock_file) { + return false; + } + + return true; +} + +static controller_connector_ctx_t* +controller_connector_init(xcecd_context_t *ctx) { + controller_connector_ctx_t *cc_ctx = + calloc(1, sizeof(controller_connector_ctx_t)); + if (!cc_ctx) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for controller connector context"); + return NULL; + } + + cc_ctx->running = true; + cc_ctx->join_interval = ctx->join_interval; + cc_ctx->remote_host = strdup(ctx->remote_host); + if (!cc_ctx->remote_host) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for remote host"); + FREEUP(cc_ctx); + return NULL; + } +#ifdef VIRTUAL_CLUSTER + cc_ctx->virtual_name = strdup(ctx->virtual_name); + if (!cc_ctx->virtual_name) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to allocate memory for virtual name"); + FREEUP(cc_ctx->remote_host); + FREEUP(cc_ctx); + return NULL; + } +#else + cc_ctx->virtual_name = NULL; +#endif + cc_ctx->remote_port = ctx->remote_port; + cc_ctx->thread_pool = ctx->thread_pool; + cc_ctx->process_manager = ctx->process_manager; + cc_ctx->worker_list = (void **) &ctx->worker_list; + + return cc_ctx; +} + +static void init_host_attrs(void) { + host_attrs.cpufactor = 1.0; + + if (xcecd_ctx.hardware_monitor) { + host_attrs.maxcpus = hardware_monitor_get_cpu_cores(xcecd_ctx.hardware_monitor); + host_attrs.maxmem = hardware_monitor_get_total_memory(xcecd_ctx.hardware_monitor); + } + + host_attrs.maxswap = 1024; + host_attrs.maxtmp = 2048; + host_attrs.ndisks = 1; + + host_attrs.hostmodel = strdup("Intel64"); + host_attrs.hosttype = strdup("LINUX64"); +} + +int xcecd_init(const xcecd_config_t* config) { + XCECD_LOG(XCE_LOG_DEBUG, "Initializing XCECD daemon"); + if (!config) { + XCECD_LOG(XCE_LOG_FATAL, "Invalid configuration."); + return -1; + } + + if (!xcecd_config_is_valid(config)) { + return -1; + } + + memset(&xcecd_ctx, 0, sizeof(xcecd_context_t)); + + char *log_file = strdup(config->log_file); + + xcecd_ctx.running = false; + xcecd_ctx.remote_host = strdup(config->remote_host); + xcecd_ctx.work_dir = strdup(config->work_dir); + xcecd_ctx.db_path = strdup(config->db_path); + xcecd_ctx.log_dir = strdup(dirname(log_file)); + FREEUP(log_file); + + xcecd_ctx.domain_sock = config->domain_sock; +#ifdef VIRTUAL_CLUSTER + xcecd_ctx.virtual_name = strdup(config->virutal_name); +#endif + xcecd_ctx.remote_port = config->remote_port ? config->remote_port : REMOTE_PORT; + xcecd_ctx.local_port = config->port; + xcecd_ctx.update_interval = config->update_interval; + xcecd_ctx.join_interval = config->join_interval; + xcecd_ctx.debug_level = config->debug_level; + + /* thread pool initializing */ + xcecd_ctx.thread_pool = thread_pool_create(config->thread_pool_size); + if (!xcecd_ctx.thread_pool) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to create thread pool"); + goto cleanup_remote_host; + } + + /* process manager initializing */ + xcecd_ctx.process_manager = process_manager_create(); + if (!xcecd_ctx.process_manager) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to create process manager"); + goto cleanup_thread_pool; + } + + /* system monitor creating */ + xcecd_ctx.system_monitor = system_monitor_create(); + if (!xcecd_ctx.system_monitor) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to create system monitor"); + goto cleanup_process_manager; + } + + /* hardware monitor creating */ + xcecd_ctx.hardware_monitor = hardware_monitor_create(config->acct_file); + if (!xcecd_ctx.hardware_monitor) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to create hardware monitor"); + goto cleanup_system_monitor; + } + + /* listening port initialization */ + if (0 != xcecd_ctx.local_port) { + xcecd_ctx.network = network_handler_create(xcecd_ctx.local_port); + if (xcecd_ctx.network == NULL) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to create network handler"); + goto cleanup_network; + } + } + + xcecd_ctx.resource_sync = resource_sync_create(); + if (!xcecd_ctx.resource_sync) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to create resource sync module"); + goto cleanup_network; + } + + xcecd_ctx.normal_restart = xcecd_check_restart_type(); + + XCECD_LOG(XCE_LOG_DEBUG, "XCECD daemon initialized successfully"); + + xcecd_ctx.worker_list = NULL; + xcecd_ctx.db = NULL; + xcecd_ctx.running = true; + + return 0; + +cleanup_network: + network_handler_destroy(xcecd_ctx.network); +cleanup_system_monitor: + system_monitor_destroy(xcecd_ctx.system_monitor); +cleanup_process_manager: + process_manager_destroy(xcecd_ctx.process_manager); +cleanup_thread_pool: + thread_pool_destroy(xcecd_ctx.thread_pool); +cleanup_remote_host: + free(xcecd_ctx.remote_host); + free(xcecd_ctx.work_dir); + free(xcecd_ctx.db_path); + free(xcecd_ctx.log_dir); + + return -1; +} + +int xcecd_start(void) { + + if (!xcecd_ctx.running) { + return -1; + } + + if ((0 != xcecd_ctx.local_port) && + network_handler_start(xcecd_ctx.network) != 0) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to start network handler"); + return -1; + } + + if (!resource_sync_start(xcecd_ctx.resource_sync)) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to start resource sync module"); + return -1; + } + + hardware_monitor_update(xcecd_ctx.hardware_monitor); + + system_monitor_update(xcecd_ctx.system_monitor); + + process_manager_update(xcecd_ctx.process_manager); + +#ifndef VIRTUAL_CLUSTER + /* restart or recover stage, must before connect to controller */ + if (access(xcecd_ctx.db_path, F_OK) != 0) { + if (!xcecd_db_init(&xcecd_ctx.db, xcecd_ctx.db_path)) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to initialize database"); + return -1; + } + } else { + + if (sqlite3_open(xcecd_ctx.db_path, &xcecd_ctx.db) != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to open database: %s", + sqlite3_errmsg(xcecd_ctx.db)); + FREEUP(xcecd_ctx.db); + return false; + } + + /* need recover, restore worker list */ + xcecd_db_get_running_workers(xcecd_ctx.db, + &xcecd_ctx.worker_list); + } +#endif + + init_host_attrs(); + + cc_ctx = controller_connector_init(&xcecd_ctx); + if (!cc_ctx) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to initialize controller connector context"); + return -1; + } + + if (!controller_connector_start()) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to start controller connector"); + return -1; + } + +#ifndef VIRTUAL_CLUSTER + /* after recovery we need to sync job status with controller */ + if (!resend_job_status(xcecd_ctx.worker_list)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to resend job status to controller"); + return -1; + } +#endif + + if (start_controller_connector_thread(connector_to_controller, NULL) != 0) { + XCECD_LOG(XCE_LOG_FATAL, "Failed to start controller connector thread"); + return -1; + } + + XCECD_LOG(XCE_LOG_DEBUG, + "XCECD daemon started successfully"); + + return 0; +} + +void xcecd_stop(void) { + XCECD_LOG(XCE_LOG_DEBUG, "Stopping XCECD daemon..."); + xcecd_ctx.running = false; +} + +void xcecd_cleanup(void) { + if (xcecd_ctx.domain_sock >= 0) { + close(xcecd_ctx.domain_sock); + } + if (xcecd_ctx.network) { + network_handler_destroy(xcecd_ctx.network); + } + if (xcecd_ctx.process_manager) { + process_manager_destroy(xcecd_ctx.process_manager); + } + if (xcecd_ctx.system_monitor) { + system_monitor_destroy(xcecd_ctx.system_monitor); + } + if (xcecd_ctx.hardware_monitor) { + hardware_monitor_destroy(xcecd_ctx.hardware_monitor); + } + if (xcecd_ctx.thread_pool) { + thread_pool_destroy(xcecd_ctx.thread_pool); + } + if (xcecd_ctx.resource_sync) { + resource_sync_stop(xcecd_ctx.resource_sync); + resource_sync_destroy(xcecd_ctx.resource_sync); + } + if (xcecd_ctx.db) { + xcecd_db_close(xcecd_ctx.db); + } + cleanup_worker_list(&xcecd_ctx.worker_list, + (sqlite3*)xcecd_ctx.db); + + FREEUP(xcecd_ctx.process_manager); + FREEUP(xcecd_ctx.remote_host); + FREEUP(xcecd_ctx.work_dir); + FREEUP(xcecd_ctx.db_path); + FREEUP(xcecd_ctx.log_dir); +#ifdef VIRTUAL_CLUSTER + FREEUP(xcecd_ctx.virtual_name); +#endif + memset(&xcecd_ctx, 0, sizeof(xcecd_ctx)); + + XCECD_LOG(XCE_LOG_DEBUG, "XCECD daemon cleaned up."); +} + +void xcecd_prepare_restart() { + int fd = open(RESTART_FLAG_FILE, O_CREAT | O_WRONLY, 0644); + if (fd != -1) { + close(fd); + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to create restart flag file."); + } +} + +bool xcecd_check_restart_type(void) { + if (access(RESTART_FLAG_FILE, F_OK) == 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "XCECD daemon is performing planned restart."); + unlink(RESTART_FLAG_FILE); + return true; + } else { + if (access(xcecd_ctx.db_path, F_OK) == 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "XCECD daemon is performing unplanned restart."); + return false; + } else { + XCECD_LOG(XCE_LOG_DEBUG, + "XCECD daemon is starting for the first time."); + return true; + } + } +} \ No newline at end of file diff --git a/src/worker/src/xcecd_cgroups.c b/src/worker/src/xcecd_cgroups.c new file mode 100644 index 00000000..b8751882 --- /dev/null +++ b/src/worker/src/xcecd_cgroups.c @@ -0,0 +1,1127 @@ +#include "xcecd_cgroups.h" +#include "xcecd_utils.h" +#include "xcelog.h" +#include + +#ifndef CPU_SETSIZE +#define CPU_SETSIZE 1024 +#endif + +/** + * @brief start worker with resource limits + * @param command command with arguments + * @param limits resource limits (NULL if no limits) + * @param unit_name_prefix unit name prefix, NULL for default prefix + * @param job_id job id + * @param extra_args extra command line arguments (NULL if none) + * @return unit name if success, NULL if failed + */ +char* start_systemd_job(const char* command, + const res_limits_t* limits, + const char* unit_name_prefix, + uint64_t job_id, + char* extra_args) +{ + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL; + char *unit_name = NULL; + char service_name[256]; + + // 生成唯一的unit名 + const char* prefix = unit_name_prefix ? unit_name_prefix : "xcecd-worker"; + snprintf(service_name, + sizeof(service_name), + "%s-%lu.service", + prefix, + job_id); + + // 连接到system bus + int r = sd_bus_open_system(&bus); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to connect to system bus: %s", + strerror(-r)); + goto finish; + } + + // 创建方法调用消息 + r = sd_bus_message_new_method_call(bus, &m, + "org.freedesktop.systemd1", // 服务 + "/org/freedesktop/systemd1", // 对象路径 + "org.freedesktop.systemd1.Manager", // 接口 + "StartTransientUnit"); // 方法 + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to create message: %s", + strerror(-r)); + goto finish; + } + + // 设置参数 (unit name, mode) + r = sd_bus_message_append(m, + "ss", + service_name, + "fail"); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to append unit name: %s", + strerror(-r)); + goto finish; + } + + // 开始单元属性数组 + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to open container: %s", + strerror(-r)); + goto finish; + } + + // 添加ExecStart属性 + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "ExecStart"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "a(sasb)"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'a', "(sasb)"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'r', "sasb"); + if (r < 0) goto finish; + + // 命令路径 + r = sd_bus_message_append(m, "s", "/bin/sh"); + if (r < 0) goto finish; + + // 命令参数 + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "/bin/sh"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "-c"); + if (r < 0) goto finish; + + if (extra_args && strlen(extra_args) > 0) { + char full_command[1024]; + snprintf(full_command, sizeof(full_command), + "exec %s %s", command, extra_args); + + r = sd_bus_message_append(m, "s", full_command); + } else { + r = sd_bus_message_append(m, "s", command); + } + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭字符串数组 + if (r < 0) goto finish; + + // 不使用终端 + r = sd_bus_message_append(m, "b", 0); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭 "sasb" 结构 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭 ExecStart 数组 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭 ExecStart 属性 + if (r < 0) goto finish; + + // 设置保持服务运行 + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "RemainAfterExit"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "b"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "b", 1); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭 RemainAfterExit 属性 + if (r < 0) goto finish; + + // 设置 StandardInput/Output/Error 为简单的重定向 + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "StandardInput"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "s"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "null"); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) goto finish; + + // 设置 KillMode 为 process,不影响子进程 + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "KillMode"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "s"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "control-group"); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) goto finish; + + // 添加KillSignal属性 - 设置默认的终止信号 + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "KillSignal"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "i"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "i", SIGTERM); // 使用 SIGTERM 作为默认终止信号 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + + // 如果提供了资源限制,设置相应的属性 + if (limits) { + + // === User Name === + if (limits->user_name && strlen(limits->user_name) > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "User"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "s"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", limits->user_name); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // === CPU 限制 === + + // CPU time in seconds + if (limits->cpu_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "LimitCPU"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "t", limits->cpu_limit); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // Core number limit + if (limits->core_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "AllowedCPUs"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "ay"); + if (r < 0) goto finish; + + // 构造 CPU bitmask, 每 8bit 表示一个字节 + uint8_t mask[128]; // 可支持 1024 cores + memset(mask, 0, sizeof(mask)); + + for (int cpu = 0; cpu < limits->core_limit; cpu++) { + int idx = cpu / 8; + int bit = cpu % 8; + mask[idx] |= (1 << bit); + } + + int mask_size = (limits->core_limit + 7) / 8; // 向上取整字节数 + + r = sd_bus_message_append_array(m, 'y', mask, mask_size); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) goto finish; + } + + // CPUQuota + if (limits->cpu_quota > 0 && limits->cpu_quota <= 100) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "CPUQuotaPerSecUSec"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + // CPUQuotaPerSecUSec: 100% = 1s = 1,000,000 usec + uint64_t quota_usec = (limits->cpu_quota * 10000ULL); + // 100% -> 1_000_000, 50% -> 500_000 + + r = sd_bus_message_append(m, "t", quota_usec); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // CPUPeriod + if (limits->cpu_period > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "CPUPeriodUSec"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "t", limits->cpu_period); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // CPUWeight + if (limits->cpu_weight > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "CPUWeight"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "u"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "u", limits->cpu_weight); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // CPUAffinity + if (limits->cpu_affinity && strlen(limits->cpu_affinity) > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "CPUAffinity"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "ay"); + if (r < 0) goto finish; + + // 处理CPU亲和性字符串,这是一个简化的实现,实际上需要更复杂的处理 + // 将CPU亲和列表解析为CPU掩码,这里只是示例 + // 格式如:"0,1,2" -> 00000111b + unsigned char cpu_mask[CPU_SETSIZE/8] = {0}; + char *affinity_str = strdup(limits->cpu_affinity); + char *token = strtok(affinity_str, ","); + while (token != NULL) { + int cpu_id = atoi(token); + if (cpu_id >= 0 && cpu_id < CPU_SETSIZE) { + cpu_mask[cpu_id/8] |= (1 << (cpu_id % 8)); + } + token = strtok(NULL, ","); + } + free(affinity_str); + + // 发送CPU掩码 + r = sd_bus_message_append_array(m, 'y', cpu_mask, sizeof(cpu_mask)); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // === 内存限制 === + + // MemoryMax (rss_limit) + if (limits->memory_max > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "MemoryMax"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + // covert MB to bytes + r = sd_bus_message_append(m, "t", limits->memory_max * 1024 * 1024); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // MemoryHigh + if (limits->memory_high > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "MemoryHigh"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "t", limits->memory_high); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // MemorySwapMax + if (limits->memory_swap_max > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "MemorySwapMax"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + // covert MB to bytes + r = sd_bus_message_append(m, "t", limits->memory_swap_max * 1024 * 1024); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // File size limit + if (limits->fsize_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(m, "s", "LimitFSIZE"); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) + goto finish; + + // Convert MB to bytes + uint64_t fsize_bytes = limits->fsize_limit * 1024 * 1024; + r = sd_bus_message_append(m, "t", fsize_bytes); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) + goto finish; + } + + // Core file size limit + if (limits->core_size_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(m, "s", "LimitCORE"); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) + goto finish; + + // Convert MB to bytes + uint64_t core_bytes = limits->core_size_limit * 1024 * 1024; + r = sd_bus_message_append(m, "t", core_bytes); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) + goto finish; + } + + // Stack size limit + if (limits->stack_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(m, "s", "LimitSTACK"); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) + goto finish; + + // Convert MB to bytes + uint64_t stack_bytes = limits->stack_limit * 1024 * 1024; + r = sd_bus_message_append(m, "t", stack_bytes); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) + goto finish; + } + + // Data size limit + if (limits->data_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(m, "s", "LimitDATA"); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) + goto finish; + + // Convert MB to bytes + uint64_t data_bytes = limits->data_limit * 1024 * 1024; + r = sd_bus_message_append(m, "t", data_bytes); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) + goto finish; + } + + // === IO 限制 === + + // IOWeight + if (limits->io_weight > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "IOWeight"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "t", limits->io_weight); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // Run time limit + if (limits->run_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(m, "s", "RuntimeMaxUSec"); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) + goto finish; + + uint64_t runtime_usec = limits->run_limit * 1000000; + r = sd_bus_message_append(m, "t", runtime_usec); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); + if (r < 0) + goto finish; + } + + // IO设备特定限制 + if (limits->io_device_path && strlen(limits->io_device_path) > 0) { + // IOReadBandwidthMax + if (limits->io_read_bps_max > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "IOReadBandwidthMax"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "a(st)"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'a', "(st)"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'r', "st"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", limits->io_device_path); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "t", limits->io_read_bps_max); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭 "st" 结构 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭数组 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // IOWriteBandwidthMax + if (limits->io_write_bps_max > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "IOWriteBandwidthMax"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "a(st)"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'a', "(st)"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'r', "st"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", limits->io_device_path); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "t", limits->io_write_bps_max); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭 "st" 结构 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭数组 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + } + + // === 任务限制 === + + // Thread limit + if (limits->tasks_max > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "TasksMax"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "t", limits->tasks_max); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + + // Process limit + if (limits->process_limit > 0) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(m, "s", "LimitNPROC"); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(m, 'v', "t"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(m, "t", limits->process_limit); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) + goto finish; + } + + // === OOM设置 === + + // OOMScoreAdjust + if (limits->oom_score_adjust >= -1000 && limits->oom_score_adjust <= 1000) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "s", "OOMScoreAdjust"); + if (r < 0) goto finish; + + r = sd_bus_message_open_container(m, 'v', "i"); + if (r < 0) goto finish; + + r = sd_bus_message_append(m, "i", limits->oom_score_adjust); + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭变体 + if (r < 0) goto finish; + + r = sd_bus_message_close_container(m); // 关闭属性 + if (r < 0) goto finish; + } + } + + r = sd_bus_message_close_container(m); // 关闭属性数组 + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to close properties: %s", + strerror(-r)); + goto finish; + } + + // 添加辅助单元数组 (空) + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to append aux units: %s", + strerror(-r)); + goto finish; + } + + // 发送消息并获取响应 + // const char *job_path = NULL; + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to call method: %s", + error.message); + goto finish; + } + + // 成功启动unit,返回unit名称 + unit_name = strdup(service_name); + +finish: + sd_bus_error_free(&error); + sd_bus_message_unref(m); + sd_bus_unref(bus); + + return unit_name; +} + +/** + * @brief Replace '.' and '-' characters in a string with '_2e' and '_2d' respectively. + * @param str input string + * @return new string with replaced characters (must be freed by caller) + */ +static char *replace_chars(const char *str) { + if (!str) { + return NULL; + } + + size_t str_len = strlen(str); + size_t new_len = 0; + for (size_t i = 0; i < str_len; i++) { + if (str[i] == '.' || str[i] == '-') { + new_len += 3; + } else { + new_len++; + } + } + + char *new_str = (char *)calloc(new_len + 1, sizeof(char)); + if (!new_str) { + return NULL; + } + + size_t j = 0; + for (size_t i = 0; i < str_len; i++) { + if (str[i] == '.') { + new_str[j++] = '_'; + new_str[j++] = '2'; + new_str[j++] = 'e'; + } else if (str[i] == '-' ) { + new_str[j++] = '_'; + new_str[j++] = '2'; + new_str[j++] = 'd'; + } else { + new_str[j++] = str[i]; + } + } + new_str[j] = '\0'; + + return new_str; +} + +/** + * @brief Get PID (MainPID) of a systemd job + * @param unit_name unit name + * @return PID if success, -1 if failed + */ +pid_t get_systemd_job_pid(const char* unit_name) { + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + //sd_bus_message *reply = NULL; + uint32_t pid = 0; + + int r = sd_bus_open_system(&bus); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to connect to system bus: %s", + strerror(-r)); + return -1; + } + + char* new_unit_name = replace_chars(unit_name); + + char unit_path[256]; + snprintf(unit_path, + sizeof(unit_path), + "/org/freedesktop/systemd1/unit/%s", + new_unit_name); + FREEUP(new_unit_name); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", // service + unit_path, // need to replace '.' and '-' with '_2e' and '_2d' + "org.freedesktop.systemd1.Service", // interface + "MainPID", // property + &error, + 'u', + &pid); + + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to get PID: %s", + error.message); + pid = 0; + } + + sd_bus_error_free(&error); + sd_bus_unref(bus); + + return (pid_t)pid; +} + +/** + * @brief Stop a systemd job + * @param unit_name unit name + * @return 0 if success, -1 if failed + */ +int stop_systemd_job(const char* unit_name) { + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = NULL; + char service_path[512]; + int r; + + snprintf(service_path, + sizeof(service_path), + "/run/systemd/transient/%s", + unit_name); + if (access(service_path, F_OK) != 0) { + return 0; + } + + r = sd_bus_open_system(&bus); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to connect to system bus: %s", + strerror(-r)); + return -1; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", // service + "/org/freedesktop/systemd1", // object path + "org.freedesktop.systemd1.Manager", // interface + "StopUnit", // method + &error, + &reply, + "ss", // parameters type + unit_name, // unit name + "replace"); // mode + + if (r < 0) { + XCECD_LOG(XCE_LOG_WARN, + "Failed to stop unit: %s", + error.message); + } + + if (access(service_path, F_OK) == 0) { + unlink(service_path); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(reply); + sd_bus_unref(bus); + + return r >= 0 ? 0 : -1; +} + +/** + * @brief check if a worker service is active + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return true if active, false if not active + */ +bool is_systemd_job_active(const char* unit_name) { + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + char *status = NULL; + int r; + bool active = false; + + r = sd_bus_open_system(&bus); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to connect to system bus: %s", + strerror(-r)); + return false; + } + + char* new_unit_name = replace_chars(unit_name); + + char unit_path[256]; + snprintf(unit_path, + sizeof(unit_path), + "/org/freedesktop/systemd1/unit/%s", + new_unit_name); + FREEUP(new_unit_name); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", // service + unit_path, // need to replace '.' and '-' with '_2e' and '_2d' + "org.freedesktop.systemd1.Unit", // interface + "ActiveState", // property + &error, + &status); + + if (r < 0) { + XCECD_LOG(XCE_LOG_WARN, + "Failed to get ActiveState: %s", + error.message); + } else { + active = strcmp(status, "active") == 0; + } + + sd_bus_error_free(&error); + sd_bus_unref(bus); + free(status); + + return active; +} + +/** + * @brief Get exit code of a systemd job + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return exit code if success, -1 if failed + */ +int get_systemd_job_exit_code(const char* unit_name) { + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + int exit_code = -1; + + r = sd_bus_open_system(&bus); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to connect to system bus: %s", + strerror(-r)); + return -1; + } + + char* new_unit_name = replace_chars(unit_name); + + char unit_path[256]; + snprintf(unit_path, + sizeof(unit_path), + "/org/freedesktop/systemd1/unit/%s", + new_unit_name); + FREEUP(new_unit_name); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", // service + unit_path, // need to replace '.' and '-' with '_2e' and '_2d' + "org.freedesktop.systemd1.Service", // interface + "ExecMainStatus", // property + &error, + 'i', + &exit_code); + + if (r < 0) { + XCECD_LOG(XCE_LOG_WARN, + "Failed to get ExecMainStatus: %s", + error.message); + exit_code = -1; + } + + sd_bus_error_free(&error); + sd_bus_unref(bus); + + return exit_code; +} + +/** + * @brief Get systemd job status text + * @param unit_name unit name + * @return status text if success, NULL if failed. + * Caller must free the returned string. + */ +char* get_systemd_job_status_text(const char* unit_name) { + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + char *status_text = NULL; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to connect to system bus: %s", strerror(-r)); + return NULL; + } + + char* new_unit_name = replace_chars(unit_name); + char unit_path[256]; + snprintf(unit_path, sizeof(unit_path), + "/org/freedesktop/systemd1/unit/%s", new_unit_name); + FREEUP(new_unit_name); + + r = sd_bus_get_property_string(bus, + "org.freedesktop.systemd1", + unit_path, + "org.freedesktop.systemd1.Unit", + "StatusText", + &error, + &status_text); + + if (r < 0) { + XCECD_LOG(XCE_LOG_WARN, + "Failed to get StatusText for %s: %s", + unit_name, error.message); + sd_bus_error_free(&error); + sd_bus_unref(bus); + return NULL; + } + + sd_bus_error_free(&error); + sd_bus_unref(bus); + + return status_text; +} \ No newline at end of file diff --git a/src/worker/src/xcecd_conf.c b/src/worker/src/xcecd_conf.c new file mode 100644 index 00000000..b8ff901c --- /dev/null +++ b/src/worker/src/xcecd_conf.c @@ -0,0 +1,231 @@ +#include "xcecd_conf.h" +#include "xcelog.h" +#include +#include +#include +#include + + +static const cyaml_schema_value_t management_host_schema = { + .type = CYAML_STRING, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(char*), + .string = { + .min = 0, + .max = MAX_HOSTNAME_LEN, + }, +}; + +static const cyaml_schema_field_t worker_config_fields_schema[] = { + { + .key = "management_list", + .data_offset = offsetof(worker_config_t, management_list), + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(char*), + .sequence = { + .entry = &management_host_schema, + .min = 0, + .max = CYAML_UNLIMITED, + }, + }, + .count_offset = offsetof(worker_config_t, management_list_count), + .count_size = sizeof(uint32_t), + }, + { + .key = "controller_port", + .data_offset = offsetof(worker_config_t, controller_port), + .value = { + .type = CYAML_UINT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(uint16_t), + }, + }, + { + .key = "log_level", + .data_offset = offsetof(worker_config_t, log_level), + .value = { + .type = CYAML_STRING, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(char*), + .string = { + .min = 0, + .max = MAX_LOG_LEVEL_LEN, + }, + }, + }, + { + .key = "log_dir", + .data_offset = offsetof(worker_config_t, log_dir), + .value = { + .type = CYAML_STRING, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(char*), + .string = { + .min = 0, + .max = MAX_PATH_LEN, + }, + }, + }, + { .key = NULL } +}; + +static const cyaml_schema_field_t root_fields_schema[] = { + { + .key = "workerconf", + .value = { + .type = CYAML_MAPPING, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(worker_config_t), + .mapping = {worker_config_fields_schema}, + }, + }, + CYAML_FIELD_END +}; + +static const cyaml_schema_value_t root_schema = { + .type = CYAML_MAPPING, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(worker_config_t), + .mapping = {root_fields_schema}, +}; + +static const cyaml_config_t cyaml_config = { + .log_fn = cyaml_log, + .mem_fn = cyaml_mem, + .log_level = CYAML_LOG_WARNING, +}; + +config_result_t* config_load(const char *config_path) { + config_result_t *result = calloc(1, sizeof(config_result_t)); + if (!result) { + return NULL; + } + + result->error_code = CONFIG_SUCCESS; + result->config = NULL; + + FILE *file = fopen(config_path, "r"); + if (!file) { + result->error_code = CONFIG_ERROR_FILE_NOT_FOUND; + snprintf(result->error_message, sizeof(result->error_message), + "Cannot open config file '%s': %s", config_path, strerror(errno)); + return result; + } + fclose(file); + + cyaml_err_t err = cyaml_load_file(config_path, &cyaml_config, + &root_schema, (void **)&result->config, NULL); + + if (err != CYAML_OK) { + result->error_code = CONFIG_ERROR_PARSE_FAILED; + snprintf(result->error_message, sizeof(result->error_message), + "YAML parse error: %s", cyaml_strerror(err)); + return result; + } + + if (!result->config) { + result->error_code = CONFIG_ERROR_INVALID_DATA; + snprintf(result->error_message, sizeof(result->error_message), + "Configuration data is null"); + return result; + } + + if (result->config->controller_port == 0) { + result->error_code = CONFIG_ERROR_INVALID_DATA; + snprintf(result->error_message, sizeof(result->error_message), + "controller_port must be greater than 0"); + return result; + } + + if (!result->config->log_level || strlen(result->config->log_level) == 0) { + result->error_code = CONFIG_ERROR_INVALID_DATA; + snprintf(result->error_message, sizeof(result->error_message), + "log_level cannot be empty"); + return result; + } + + if (!result->config->log_dir || strlen(result->config->log_dir) == 0) { + result->error_code = CONFIG_ERROR_INVALID_DATA; + snprintf(result->error_message, sizeof(result->error_message), + "log_dir cannot be empty"); + return result; + } + + return result; +} + +config_result_t* config_load_default(void) { + return config_load(DEFAULT_CONFIG_PATH); +} + +void config_free(config_result_t *result) { + if (!result) return; + + if (result->config) { + cyaml_free(&cyaml_config, &root_schema, result->config, 0); + } + free(result); +} + +const char* config_error_string(config_error_t error) { + switch (error) { + case CONFIG_SUCCESS: + return "Success"; + case CONFIG_ERROR_FILE_NOT_FOUND: + return "Configuration file not found"; + case CONFIG_ERROR_PARSE_FAILED: + return "YAML parsing failed"; + case CONFIG_ERROR_INVALID_DATA: + return "Invalid configuration data"; + case CONFIG_ERROR_MEMORY: + return "Memory allocation error"; + case CONFIG_ERROR_UNKNOWN: + default: + return "Unknown error"; + } +} + +int level_to_int(const char *level) { + if (!level) return XCE_LOG_INFO; // Default to INFO if NULL + + if (strcmp(level, "debug3") == 0) { + return XCE_LOG_DEBUG3; + } else if (strcmp(level, "debug2") == 0) { + return XCE_LOG_DEBUG2; + } else if (strcmp(level, "debug") == 0) { + return XCE_LOG_DEBUG; + } else if (strcmp(level, "notice") == 0) { + return XCE_LOG_NOTICE; + } else if (strcmp(level, "info") == 0) { + return XCE_LOG_INFO; + } else if (strcmp(level, "warn") == 0) { + return XCE_LOG_WARN; + } else if (strcmp(level, "error") == 0) { + return XCE_LOG_ERROR; + } else if (strcmp(level, "fatal") == 0) { + return XCE_LOG_FATAL; + } else if (strcmp(level, "off") == 0) { + return XCE_LOG_OFF; + } else { + return XCE_LOG_INFO; // Default to INFO for unknown levels + } +} + +void config_print(const worker_config_t *config) { + if (!config) { + printf("Configuration is NULL\n"); + return; + } + + printf("Worker Configuration:\n"); + printf(" Controller Port: %u\n", config->controller_port); + printf(" Log Level: %s\n", config->log_level); + printf(" Log Directory: %s\n", config->log_dir); + printf(" Management List (%u hosts):\n", config->management_list_count); + + for (uint32_t i = 0; i < config->management_list_count; i++) { + printf(" - %s\n", config->management_list[i].hostname); + } +} \ No newline at end of file diff --git a/src/worker/src/xcecd_db.c b/src/worker/src/xcecd_db.c new file mode 100644 index 00000000..56fd3948 --- /dev/null +++ b/src/worker/src/xcecd_db.c @@ -0,0 +1,397 @@ +#include +#include +#include "sqlite3.h" +#include "xcecd_db.h" +#include "xcecd_utils.h" +#include "xcecd_worker_utils.h" +#include "xcelog.h" + + + +#define CREATE_TABLE_SQL \ + "CREATE TABLE IF NOT EXISTS workers (" \ + "id INTEGER PRIMARY KEY AUTOINCREMENT," \ + "pid INTEGER NOT NULL," \ + "status INTEGER NOT NULL," \ + "job_id INTEGER NOT NULL," \ + "job_status INTEGER NOT NULL" \ + ");" + +/** + * @brief Initialize the database + * + * @param db Pointer to the database structure + * @param db_path Path to the database file + * @return true if success, false if failed + */ +bool xcecd_db_init(xcecd_db_t** db, + const char* db_path) +{ + if (*db || *db || !db_path) { + return false; + } + +#if 0 + *db = calloc(1, sizeof(xcecd_db_t*)); + if (!*db) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for database"); + return false; + } +#endif + + if (sqlite3_open(db_path, db) != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to open database: %s", + sqlite3_errmsg(*db)); + FREEUP(*db); + return false; + } + + char sql[256]; + snprintf(sql, sizeof(sql), + "SELECT name FROM sqlite_master WHERE type='table' AND name='workers';"); + + char *err_msg = NULL; + int rc = sqlite3_exec(*db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to check table existence: %s", + err_msg); + sqlite3_free(err_msg); + sqlite3_close(*db); + FREEUP(*db); + return false; + } + + sqlite3_stmt *stmt = NULL; + rc = sqlite3_prepare_v2(*db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to prepare statement: %s", + sqlite3_errmsg(*db)); + sqlite3_close(*db); + FREEUP(*db); + return false; + } + + if (sqlite3_step(stmt) != SQLITE_ROW) { + sqlite3_finalize(stmt); + rc = sqlite3_exec(*db, CREATE_TABLE_SQL, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to create table: %s", + err_msg); + sqlite3_free(err_msg); + sqlite3_close(*db); + FREEUP(*db); + return false; + } + } + + //if (stmt) { + // sqlite3_finalize(stmt); + //} + + XCECD_LOG(XCE_LOG_DEBUG, "Database initialized successfully"); + + return true; +} + +/** + * @brief close the database + * @param db Pointer to the database structure + */ +void xcecd_db_close(xcecd_db_t* db) { + if (db) { + sqlite3_close(db); + } +} + +/** + * @brief Insert a worker into the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @param worker_status Status of the worker + * @param job_id Job ID associated with the worker + * @param job_status Status of the job associated with the worker + * @return true if success, false if failed + */ +bool xcecd_db_insert_worker(xcecd_db_t* db, + int worker_pid, + int worker_status, + uint64_t job_id, + int job_status) +{ +#ifdef VIRTUAL_CLUSTER + return true; +#endif + + if (!db) { + return false; + } + + char *err_msg = NULL; + char sql[256]; + snprintf(sql, sizeof(sql), + "INSERT OR REPLACE INTO workers (pid, status, job_id, job_status) VALUES (%d, %d, %lu, %d);", + worker_pid, + worker_status, + job_id, + job_status); + + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to insert worker: %s", + err_msg); + sqlite3_free(err_msg); + return false; + } + + XCECD_LOG(XCE_LOG_DEBUG, + "Worker %d inserted successfully", + worker_pid); + + return true; +} + +/** + * @brief Update a worker in the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @param worker_status Status of the worker + * @param job_status Status of the job associated with the worker + * @return true if success, false if failed + */ +bool xcecd_db_update_worker(sqlite3* db, + int worker_pid, + int worker_status, + int job_status) +{ +#ifdef VIRTUAL_CLUSTER + return true; +#endif + + if (!db) { + return false; + } + + char *err_msg = NULL; + char sql[256]; + snprintf(sql, sizeof(sql), + "UPDATE workers SET status = %d, job_status = %d WHERE pid = %d;", + worker_status, + job_status, + worker_pid); + + XCECD_LOG(XCE_LOG_DEBUG, "Update SQL: %s", sql); + + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + int errcode = sqlite3_errcode(db); + int extended_errcode = sqlite3_extended_errcode(db); + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker: %s (errcode: %d, extended_errcode: %d)", + err_msg, + errcode, + extended_errcode); + return false; + } + + return true; +} + +/** + * @brief Delete a worker from the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @return true if success, false if failed + */ +bool xcecd_db_delete_worker(sqlite3* db, + int worker_pid) +{ +#ifdef VIRTUAL_CLUSTER + return true; +#endif + + if (!db || worker_pid <= 0) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid parameters"); + return false; + } + + char *err_msg = NULL; + char sql[256]; + snprintf(sql, sizeof(sql), + "DELETE FROM workers WHERE pid = %d;", + worker_pid); + + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to delete worker: %s", + err_msg); + sqlite3_free(err_msg); + return false; + } + + return true; +} + +/** + * @brief Get a worker from the database + * + * @param db Pointer to the database structure + * @param worker_pid PID of the worker + * @return the worker status if success, -1 if failed + */ +int xcecd_db_get_worker(xcecd_db_t* db, + int worker_pid) +{ + if (!db || worker_pid <= 0) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid parameters"); + return -1; + } + + char sql[256]; + snprintf(sql, sizeof(sql), + "SELECT * FROM workers WHERE pid = %d;", + worker_pid); + + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to prepare statement: %s", + sqlite3_errmsg(db)); + return -1; + } + + int status = -1; + if (sqlite3_step(stmt) == SQLITE_ROW) { + status = sqlite3_column_int(stmt, 1); + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to get worker: %s", + sqlite3_errmsg(db)); + } + + sqlite3_finalize(stmt); + + if (status == -1) { + return -1; + } + + return status; +} + +/** + * @brief Get running workers from the database + * + * @param db Pointer to the database structure + * @param worker_list Pointer to the worker list + * @note The worker_list should be NULL before calling this function + * @return Pointer to the worker list (db->worker_list) if success, NULL if failed + */ +bool xcecd_db_get_running_workers(xcecd_db_t* db, + xcecd_worker_t** worker_list) +{ + if (!db || *worker_list) { + return false; + } + + char sql[256]; + snprintf(sql, sizeof(sql), + "SELECT * FROM workers WHERE status = %d;", + WORKER_RUNNING); + + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to prepare statement: %s", + sqlite3_errmsg(db)); + return false; + } + + while (sqlite3_step(stmt) == SQLITE_ROW) { + + add_worker(worker_list, + sqlite3_column_int(stmt, 1), + sqlite3_column_int(stmt, 2), + sqlite3_column_int64(stmt, 3), + 0, //FIX-ME + NULL); + + XCECD_LOG(XCE_LOG_DEBUG, + "Worker %d is running", + sqlite3_column_int(stmt, 1)); + } + + sqlite3_finalize(stmt); + return true; +} + +/** + * @brief Check if the database is available + * @param db Pointer to the database structure + * @param db_path Path to the database file + * @return void and directly exit if the database is not available + */ +void xcecd_db_is_available(xcecd_db_t* db, + const char* db_path) +{ + char *msg = 0; + int rc = 0; + struct stat fileStat; + + if (!db) { + XCECD_LOG(XCE_LOG_ERROR, + "Database is not available"); + _exit(1); + } + + if (access(db_path, F_OK) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Database file %s is not available", + db_path); + _exit(1); + } + + char sql[128]; + snprintf(sql, + sizeof(sql), + "PRAGMA integrity_check;"); + + rc = sqlite3_exec(db, sql, NULL, NULL, &msg); + + if (rc != SQLITE_OK) { + XCECD_LOG(XCE_LOG_FATAL, + "Database integrity check failure, exit: %s", + msg); + sqlite3_free(msg); + sqlite3_close(db); + _exit(1); + } + + if (stat(db_path, &fileStat) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to get file status: %s", + db_path); + sqlite3_free(msg); + sqlite3_close(db); + } else { + XCECD_LOG(XCE_LOG_INFO, + "Database file %s size: %ld bytes, Last modified: %ld, File permissions: %o", + db_path, + fileStat.st_size, + fileStat.st_mtime, + fileStat.st_mode); + } + + sqlite3_free(msg); + _exit(1); +} diff --git a/src/worker/src/xcecd_dbus_manager.c b/src/worker/src/xcecd_dbus_manager.c new file mode 100644 index 00000000..b318c66e --- /dev/null +++ b/src/worker/src/xcecd_dbus_manager.c @@ -0,0 +1,459 @@ +/** + * @file xcecd_dbus_manager.c + * @brief This file contains the implementation of the D-Bus manager functions. + */ +#include "xcecd_dbus_manager.h" +#include "xcecd_utils.h" +#include "xcelog.h" + + +/** + * @brief Start worker with resource limits + * + * @param limits resource limits (NULL if no limits) + * @param job_id job id + * @param extra_args extra command line arguments (NULL if none) + * @return true if success, false if failed + */ +bool dbus_start_worker(const res_limits_t* limits, + uint64_t job_id, char* extra_args) { + char* unit_name = NULL; + bool use_default_worker = false; + + char* xce_server_dir = getenv("XCE_SERVER_DIR"); + char worker_path[1024]; + + char* default_worker_path = strdup("/opt/xce/bin/xcecd_worker"); + + if (xce_server_dir != NULL) { + snprintf(worker_path, + sizeof(worker_path), + "%s/xcecd_worker", + xce_server_dir); + + if (access(worker_path, X_OK) != 0) { + XCECD_LOG(XCE_LOG_WARN, + "xcecd_worker not found or not executable in XCE_SERVER_DIR: %s", + xce_server_dir); + + // using default path for xcecd_worker + if (access(default_worker_path, X_OK) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Default xcecd_worker not found or not executable"); + free(default_worker_path); + return false; + } + + use_default_worker = true; + } + } else { + XCECD_LOG(XCE_LOG_WARN, + "XCE_SERVER_DIR not set, using default path for xcecd_worker"); + + // using default path for xcecd_worker + if (access(default_worker_path, X_OK) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Default xcecd_worker not found or not executable"); + free(default_worker_path); + return false; + } + + use_default_worker = true; + } + + char myname[256]; + snprintf(myname, + sizeof(myname), + "xcecd-worker-%lu.service", + job_id); + /* checking if service name is exist before start service and it's a negative case */ + dbus_stop_worker(myname); + + if (use_default_worker) { + unit_name = start_systemd_job(default_worker_path, + limits, + "xcecd-worker", + job_id, + extra_args); + } else { + unit_name = start_systemd_job(worker_path, + limits, + "xcecd-worker", + job_id, + extra_args); + } + + if (!unit_name) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to start worker with resource limits"); + free(default_worker_path); + return false; + } + + free(default_worker_path); + free(unit_name); + return true; +} + +/** + * @brief Stop a worker service + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return true if success, false if failed + */ +bool dbus_stop_worker(const char* unit_name) { + if (!unit_name) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid worker service name"); + return false; + } + + int ret = stop_systemd_job(unit_name); + if (ret != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to stop worker: %s", + unit_name); + return false; + } + + char service_file[512]; + snprintf(service_file, + sizeof(service_file), + "/run/systemd/transient/%s", + unit_name); + + if (access(service_file, F_OK) == 0) { + if (remove(service_file) != 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to remove service file: %s", + service_file); + } + } + + return true; +} + +/** + * @brief Get PID of a worker service + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return PID if success, -1 if failed + */ +pid_t dbus_get_worker_pid(const char* unit_name) { + if (!unit_name) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid worker service name"); + return -1; + } + + pid_t pid = get_systemd_job_pid(unit_name); + if (pid <= 0) { + XCECD_LOG(XCE_LOG_WARN, "Failed to get worker pid"); + return -1; + } + + return pid; +} + +/** + * @brief Check if a worker service is active + * + * @param unit_name worker service name (xcecd-worker-.service) + * @return true if active, false if not active + */ +bool dbus_is_worker_active(const char* unit_name) { + if (!unit_name) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid worker service name"); + return false; + } + + bool active = is_systemd_job_active(unit_name); + if (!active) { +#ifndef VIRTUAL_CLUSTER + XCECD_LOG(XCE_LOG_DEBUG, + "Worker service is not active: %s", + unit_name); +#endif + return false; + } + + return true; +} + +/** + * @brief Update worker status in main process (single process mode) + * + * @param worker_list worker list + * @param job job information + * @param db database information + * @return XCECD_OK if success, others in execd_error if failed + */ +int dbus_workers_update(xcecd_worker_t** worker_list, + xcecd_job_t* job, + sqlite3* db) +{ + int ret = XCECD_OK; + int exit_code = 0; + + if (!worker_list) { + XCECD_LOG(XCE_LOG_ERROR, "Invalid worker list"); + return XCECD_ERROR; + } + + xcecd_worker_t* worker = *worker_list; + while (worker) { +#ifdef VIRTUAL_CLUSTER + if (worker->worker_status == WORKER_RUNNING && + worker->job_status == JOB_STARTING) { + + // use faked worker pid as job pid + job->job_pid = worker->worker_pid; + job->job_status = JOB_RUNNING; + + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_RUNNING, + job, + db); + continue; + } +#endif + + if (worker->worker_status == WORKER_DONE || + worker->worker_status == WORKER_EXITED) { + worker = worker->next; + return true; + } + + char unit_name[256]; + snprintf(unit_name, + sizeof(unit_name), + "xcecd-worker-%lu.service", + worker->job_info.jobid); + + if (!dbus_is_worker_active(unit_name)) { +#ifndef VIRTUAL_CLUSTER + XCECD_LOG(XCE_LOG_DEBUG, + "Worker %d is not active", + worker->worker_pid); +#endif + /* job done but running status hasn't received */ + if (worker->job_status == JOB_STARTING) { + worker->job_status = JOB_RUNNING; + worker->worker_status = WORKER_RUNNING; + continue; + } + + /* check if worker exit or done */ + exit_code = get_systemd_job_exit_code(unit_name); + if (exit_code == 0) { +#ifdef VIRTUAL_CLUSTER + if (check_worker_sleep_time_elapsed(worker)) { + job->exit_code = 0; + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_DONE, + job, + db); + } else if (worker->killed_signal != 0) { + job->exit_code = 128 + worker->killed_signal; + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_EXITED, + job, + db); + } + continue; +#endif + XCECD_LOG(XCE_LOG_DEBUG, + "Worker %d is done", + worker->worker_pid); + + /* stop worker service and remove file in /run/systemd/transient */ + stop_systemd_job(unit_name); + + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_DONE, + job, + db); + + } else if (exit_code > 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "Worker %d exited with code %d", + worker->worker_pid, + exit_code); + + job->exit_code = exit_code; + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_EXITED, + job, + db); + + } else { +#ifdef VIRTUAL_CLUSTER + if (check_worker_sleep_time_elapsed(worker)) { + job->exit_code = 0; + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_DONE, + job, + db); + } + continue; +#endif + XCECD_LOG(XCE_LOG_WARN, "Failed to get worker exit code"); + + job->exit_code = -1; + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_EXITED, + job, + db); + } + + /* We should keep the failed service file in /run/systemd/transient */ + stop_systemd_job(unit_name); // temp solution + + } else { + /* check if worker is done or not */ + //XCECD_LOG(XCE_LOG_DEBUG, "Checking if worker %d is running or not...", worker->worker_pid); + if (kill(worker->worker_pid, 0) != 0) { + + /* job done but running status hasn't received */ + if (worker->job_status == JOB_STARTING) { + worker->job_status = JOB_RUNNING; + worker->worker_status = WORKER_RUNNING; + continue; + } + + /* exit or done */ + exit_code = get_systemd_job_exit_code(unit_name); + + if (exit_code == 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "Worker %d is done", + worker->worker_pid); + + /* stop worker service and remove file in /run/systemd/transient */ + stop_systemd_job(unit_name); + + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_DONE, + job, + db); + + } else if (exit_code > 0) { + XCECD_LOG(XCE_LOG_DEBUG, + "Worker %d exited with code %d", + worker->worker_pid, + exit_code); + + stop_systemd_job(unit_name); // temp solution + + job->exit_code = exit_code; + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_EXITED, + job, + db); + + } else { + XCECD_LOG(XCE_LOG_WARN, "Failed to get worker exit code"); + + stop_systemd_job(unit_name); // temp solution + + job->exit_code = -1; + ret = update_worker_status(*worker_list, + worker->worker_pid, + WORKER_EXITED, + job, + db); + + } + } else { + //XCECD_LOG(XCE_LOG_DEBUG, "Worker %d is running", worker->worker_pid); + } + } + worker = worker->next; + } + + return ret; +} + +/** + * @brief Clean worker service + * + * @param worker worker information + * @param job job information + * @return true if success, false if failed + */ +bool dbus_worker_clean(xcecd_worker_t* worker, + xcecd_job_t* job) +{ +#ifndef VIRTUAL_CLUSTER + return true; +#endif + + if (!worker) { + return false; + } + + char unit_name[256]; + snprintf(unit_name, + sizeof(unit_name), + "xcecd-worker-%lu.service", + worker->job_info.jobid); + + if (dbus_is_worker_active(unit_name)) { + stop_systemd_job(unit_name); + } + + return true; +} + +/** + * @brief Go through worker list and stop all workers + * @param worker_list worker list + * @param db database information + * @return void + */ +void dbus_stop_all_workers(xcecd_worker_t** worker_list, + sqlite3* db) +{ + if (!worker_list) { + return; + } + + xcecd_job_t job = { + .job_id = 0, + .job_pid = 0, + .job_status = JOB_EXITED, + .exit_code = -1 + }; + + xcecd_worker_t* worker = *worker_list; + while (worker) { + char unit_name[256]; + snprintf(unit_name, + sizeof(unit_name), + "xcecd-worker-%lu.service", + worker->job_info.jobid); + + job.job_id = worker->job_info.jobid; + job.job_pid = worker->worker_pid; + job.job_status = JOB_EXITED; + job.exit_code = -1; + + update_worker_status(*worker_list, + worker->worker_pid, + WORKER_EXITED, + &job, + db); + + XCECD_LOG(XCE_LOG_WARN, + "Job %lu killed.", + worker->job_info.jobid); + + worker = worker->next; + } +} \ No newline at end of file diff --git a/src/worker/src/xcecd_exdata.c b/src/worker/src/xcecd_exdata.c new file mode 100644 index 00000000..3d284c7b --- /dev/null +++ b/src/worker/src/xcecd_exdata.c @@ -0,0 +1,460 @@ +/** + * @file xcecd_exdata.c + * @brief proceeding exdata for xcecd worker use + * + */ + +#include "xcecd_exdata.h" +#include "xcecd_utils.h" +#include "xcelog.h" +#include +#include + + +/** + * @brief Parses the JSON string, populates an environment variable linked list, + * and populates TransferInfo from transfer_file. + * + * @param exdata The JSON string to parse. + * @param env_vars Pointer to the head pointer of the env_var_t linked list. + * The function will add nodes to the beginning of this list. + * The list will contain allocated keys and values. + * @param transfer_infos Pointer to a transfer_info_t struct to be populated. + * The function will allocate memory for the operations array + * and strings within it. Caller must call free_transfer_info. + * @return bool true on success, false on failure during parsing or allocation. + */ +bool parse_exdata(const char* exdata, + env_var_t** env_vars, + transfer_info_t* transfer_infos, + uint64_t jobid) +{ + if (!exdata || exdata[0] == '\0') { + return true; + } + + *env_vars = NULL; + transfer_infos->ops = NULL; + transfer_infos->count = 0; + + cJSON *root = cJSON_Parse(exdata); + if (root == NULL) { + const char *error_ptr = cJSON_GetErrorPtr(); + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error parsing JSON: %s", + error_ptr ? error_ptr : "Unknown error"); + + return false; + } + + bool success = true; + cJSON *item = NULL; + + // Iterate through top-level keys + cJSON_ArrayForEach(item, root) { + const char *key = item->string; + if (key == NULL) continue; + + // --- Handle 'sub_env_vars' --- + if (strcmp(key, "sub_env_vars") == 0) { + if (!cJSON_IsObject(item)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error: 'sub_env_vars' is not a JSON object."); + + success = false; + continue; + } + + cJSON *env_var = NULL; + cJSON_ArrayForEach(env_var, item) { + const char *env_key = env_var->string; + if (env_key == NULL) continue; + + if (!cJSON_IsString(env_var) || env_var->valuestring == NULL) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error: Value for env var '%s' is not a string.", + env_key); + + success = false; + continue; + } + const char *env_value = env_var->valuestring; + + // Create new node for the linked list + env_var_t *new_node = (env_var_t *)malloc(sizeof(env_var_t)); + if (!new_node) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error allocating memory for env var node"); + + success = false; + goto cleanup; + } + + new_node->key = strdup(env_key); + new_node->value = strdup(env_value); + + if (!new_node->key || !new_node->value) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error duplicating env var strings"); + + FREEUP(new_node->key); + FREEUP(new_node->value); + FREEUP(new_node); + success = false; + goto cleanup; + } + + // Add node to the beginning of the list + new_node->next = *env_vars; + *env_vars = new_node; + } + } + // --- Handle 'transfer_file' --- + else if (strcmp(key, "transfer_file") == 0) { + if (!cJSON_IsArray(item)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error: 'transfer_file' is not a JSON array."); + + success = false; + continue; + } + + int array_size = cJSON_GetArraySize(item); + if (array_size <= 0) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "'transfer_file' array is empty."); + + continue; + } + + transfer_infos->ops = (transfer_file_t *)calloc(array_size, sizeof(transfer_file_t)); + if (transfer_infos->ops == NULL) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error allocating memory for transfer operations"); + + success = false; + goto cleanup; + } + transfer_infos->count = array_size; + + for (int i = 0; i < array_size; ++i) { + cJSON *transfer_item = cJSON_GetArrayItem(item, i); + if (!cJSON_IsObject(transfer_item)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error: Item %d in 'transfer_file' is not an object.", + i); + + success = false; // Mark as failure but continue parsing other items + continue; + } + + cJSON *op_json = cJSON_GetObjectItemCaseSensitive(transfer_item, "op"); + cJSON *local_json = cJSON_GetObjectItemCaseSensitive(transfer_item, "local_file"); + cJSON *remote_json = cJSON_GetObjectItemCaseSensitive(transfer_item, "remote_file"); + + // Validate and duplicate strings + transfer_infos->ops[i].op = (cJSON_IsString(op_json) && op_json->valuestring) ? strdup(op_json->valuestring) : NULL; + transfer_infos->ops[i].local_file = (cJSON_IsString(local_json) && local_json->valuestring) ? strdup(local_json->valuestring) : NULL; + transfer_infos->ops[i].remote_file = (cJSON_IsString(remote_json) && remote_json->valuestring) ? strdup(remote_json->valuestring) : NULL; + + if (!transfer_infos->ops[i].op || + !transfer_infos->ops[i].local_file || + !transfer_infos->ops[i].remote_file) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Missing/invalid field in transfer item %d.", + i); + + success = false; + // free_transfer_info will handle freeing partially allocated strings later + } + } + } + // --- Handle 'interactive' --- + else if (strcmp(key, "interactive") == 0) { + int interactive_value = 0; + + if (cJSON_IsString(item) && item->valuestring != NULL) { + interactive_value = atoi(item->valuestring); + } else if (cJSON_IsNumber(item)) { + interactive_value = item->valueint; + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error: 'interactive' is neither a string nor a number."); + success = false; + continue; + } + + switch (interactive_value) { + case 0: + transfer_infos->interactive_mode = NORMAL; + break; + case 1: + transfer_infos->interactive_mode = TTY; + break; + case 2: + transfer_infos->interactive_mode = XJOB; + break; + case 3: + transfer_infos->interactive_mode = DISPLAY; + break; + default: + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error: Unknown interactive mode value '%d'. Defaulting to NORMAL.", + interactive_value); + transfer_infos->interactive_mode = NORMAL; + success = false; + } + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Parsed interactive mode: %d", + transfer_infos->interactive_mode); + } + // --- Skip 'limits' --- + else if (strcmp(key, "limits") == 0) { + // limits will be handled in parse_limit + continue; + } + else { + XCECD_WORKER_LOG(XCE_LOG_NOTICE, + "Encountered unknown top-level key: '%s'. Ignoring.", key); + } + } + +cleanup: + cJSON_Delete(root); + + // If parsing failed, clean up potentially partially created lists + if (!success) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "JSON parsing finished with errors."); + + apply_env_vars(env_vars, jobid); // Use the apply function to also free + } + + return success; +} + +/** + * @brief Parses the resource limits from the given JSON string. + * @param exdata The JSON string containing the resource limits. + * @param limits Pointer to the res_limits_t structure to populate. + * @return true if parsing was successful, false otherwise. + */ +bool parse_limits(const char* exdata, res_limits_t *limits) { + if (!exdata || exdata[0] == '\0') { + return true; + } + + if (!limits) { + return false; + } + + cJSON *root = cJSON_Parse(exdata); + if (root == NULL) { + const char *error_ptr = cJSON_GetErrorPtr(); + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error parsing JSON: %s", + error_ptr ? error_ptr : "Unknown error"); + return false; + } + + bool success = false; + + cJSON *limits_obj = cJSON_GetObjectItemCaseSensitive(root, "limits"); + if (!limits_obj || !cJSON_IsObject(limits_obj)) { + // limits not exist + goto cleanup; + } + + cJSON *cpu_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "cpu_limit"); + if (cJSON_IsNumber(cpu_limit)) { + limits->cpu_limit = (uint64_t)cpu_limit->valuedouble; + } + + cJSON *core_size_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "core_limit"); + if (cJSON_IsNumber(core_size_limit)) { + limits->core_size_limit = (uint64_t)core_size_limit->valuedouble; + } + + cJSON *rss_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "rss_limit"); + if (cJSON_IsNumber(rss_limit)) { + limits->memory_max = (uint64_t)rss_limit->valuedouble; + } + + cJSON *run_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "run_limit"); + if (cJSON_IsNumber(run_limit)) { + limits->run_limit = (uint64_t)run_limit->valuedouble; + } + + cJSON *data_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "data_limit"); + if (cJSON_IsNumber(data_limit)) { + limits->data_limit = (uint64_t)data_limit->valuedouble; + } + + cJSON *swap_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "swap_limit"); + if (cJSON_IsNumber(swap_limit)) { + limits->memory_swap_max = (uint64_t)swap_limit->valuedouble; + } + + cJSON *fsize_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "fsize_limit"); + if (cJSON_IsNumber(fsize_limit)) { + limits->fsize_limit = (uint64_t)fsize_limit->valuedouble; + } + + cJSON *stack_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "stack_limit"); + if (cJSON_IsNumber(stack_limit)) { + limits->stack_limit = (uint64_t)stack_limit->valuedouble; + } + + cJSON *thread_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "thread_limit"); + if (cJSON_IsNumber(thread_limit)) { + limits->tasks_max = (uint64_t)thread_limit->valuedouble; + } + + cJSON *process_limit = cJSON_GetObjectItemCaseSensitive(limits_obj, "process_limit"); + if (cJSON_IsNumber(process_limit)) { + limits->process_limit = (uint64_t)process_limit->valuedouble; + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Parsed limits: cputime=%lu, coresize=%lu, rss=%lu, runlimit=%lu, data=%lu, swap=%lu, fsize=%lu, stack=%lu, thread=%lu, process=%lu", + limits->cpu_limit, limits->core_size_limit, limits->memory_max, limits->run_limit, limits->data_limit, limits->memory_swap_max, + limits->fsize_limit, limits->stack_limit, limits->tasks_max, limits->process_limit); + + success = true; + +cleanup: + cJSON_Delete(root); + return success; +} + +/** + * @brief Frees the memory allocated for the transfer_info_t structure. + * This includes freeing the operations array and the strings within it. + * The caller is responsible for freeing the env_var_t linked list. + * @param info Pointer to the transfer_info_t structure to free. + */ +void free_transfer_info(transfer_info_t *info) { + if (!info) { + return; + } + if (info->ops) { + for (int i = 0; i < info->count; ++i) { + free(info->ops[i].op); + free(info->ops[i].local_file); + free(info->ops[i].remote_file); + } + free(info->ops); + } + info->ops = NULL; + info->count = 0; + // Note: We don't free 'info' itself here. +} + +/** + * @brief Iterates through the environment variable linked list, sets each + * variable using setenv, and frees the list nodes and their contents. + * + * @param head Pointer to the head pointer of the EnvVarNode linked list. + * The list will be empty (*head == NULL) after this function returns. + */ +void apply_env_vars(env_var_t **env_vars, + uint64_t jobid) +{ + if (!env_vars) return; + + env_var_t *current = *env_vars; + env_var_t *next_node = NULL; + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Applying environment variables..."); + + // Set some default XCE environment variables + char jobid_str[32]; + snprintf(jobid_str, sizeof(jobid_str), "%lu", jobid); + if (setenv("XCE_JOBID", jobid_str, 1) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error setting environment variable: %s", + strerror(errno)); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Setting environment variable: XCE_JOBID=%s", + jobid_str); + } + + char jobindex_str[8]; + snprintf(jobindex_str, sizeof(jobindex_str), "%d", 0); + if (setenv("XCE_JOBINDEX", jobindex_str, 1) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error setting environment variable: %s", + strerror(errno)); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Setting environment variable: XCE_JOBINDEX=%s", + jobindex_str); + } + + while (current != NULL) { + next_node = current->next; + + if (current->key && current->value) { + // Set environment variable (overwrite if exists) + if (setenv(current->key, current->value, 1) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Error setting environment variable: %s", + strerror(errno)); + + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Setting environment variable: %s=%s", + current->key, + current->value); + } + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Warning: Skipping invalid env var node (NULL key or value)."); + } + + FREEUP(current->key); + FREEUP(current->value); + FREEUP(current); + + current = next_node; + } + + *env_vars = NULL; + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Environment variable list applied and freed."); +} + +/** + * @brief Generates exdata json string from given structure. + * @param start_info Pointer to the start_exdata_t structure. + * @return A dynamically allocated JSON string, or NULL on failure. + */ +char* generate_exdata(const start_exdata_t* start_info) { + if (!start_info || !start_info->url) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid start_exdata_t structure provided."); + return NULL; + } + + cJSON *root = cJSON_CreateObject(); + if (!root) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to create JSON object."); + return NULL; + } + + cJSON_AddStringToObject(root, "url", start_info->url); + + char *exdata_str = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + + if (!exdata_str) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to print JSON object to string."); + return NULL; + } + + return exdata_str; +} \ No newline at end of file diff --git a/src/worker/src/xcecd_sig.c b/src/worker/src/xcecd_sig.c new file mode 100644 index 00000000..00f1a2e0 --- /dev/null +++ b/src/worker/src/xcecd_sig.c @@ -0,0 +1,42 @@ +#include "xcecd_utils.h" +#include "xcelog.h" +#include +#include +#include + +void block_signal(int sig) { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, sig); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to block signal %d: %s", + sig, + strerror(errno)); + } +#if 0 + XCECD_LOG(XCE_LOG_DEBUG, + "Blocking signal %d", + sig); +#endif +} + +void unblock_signal(int sig) { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, sig); + + if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to unblock signal %d: %s", + sig, + strerror(errno)); + } +#if 0 + XCECD_LOG(XCE_LOG_DEBUG, + "Unblocking signal %d", + sig); +#endif +} + diff --git a/src/worker/src/xcecd_worker.c b/src/worker/src/xcecd_worker.c new file mode 100644 index 00000000..cc26bede --- /dev/null +++ b/src/worker/src/xcecd_worker.c @@ -0,0 +1,1429 @@ +#include "xcecd_utils.h" +#include "xcelog.h" +#include "process_manager.h" +#include "static_array.h" +#include "uv_ipc.h" +#include "uv_proc.h" +#include "xcecd_exdata.h" +#include "inter_job_utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define SOCKET_PATH "/var/run/.xcecd.sock" +#define LOG_FILE "/tmp/xcecd_worker.log" +#define XJOB_PRE_EXEC "/opt/xce/bin/startvnc" +#define XJOB_POST_EXEC "/opt/xce/bin/stopvnc" +#define CKPT_BASE "/shared/ckpt" +#define LOG_SIZE (1024 * 1024 * 100) // 100 MB +#define LOG_LEVEL 3 + + +typedef struct exdata { + env_var_t *env_vars; + transfer_info_t *transfer_info; +} exdata_t; + +exdata_t mydata = { + .env_vars = NULL, + .transfer_info = NULL +}; + +port_info_t port_info = {0}; + +static uv_proc_manager_t uv_proc_manager = {0}; +pid_t worker_pid = 0; +static uint64_t jobid = 0; +static int reported_flag = 0; +static int reconnect_retry_limit = 5; +static int reconnect_retry_interval = 1; +static int worker_reconnected = 0; +static int worker_exit_flag = 0; +static int return_code = 0; +static job_runtime_info_t job_runtime_info = {0}; +char* log_file = NULL; +start_exdata_t* start_exdata = NULL; + +// process manager will share the same event loop with ipc +uv_loop_t* uv_loop = NULL; + +static void check_and_reconnect(); +static void worker_reconnect_callback(bool connected); +static void xjob_start_vnc(); +static void xjob_stop_vnc(); +static bool check_process_status(pid_t target_pid, + int expected_signal, + int timeout_seconds); + + +static void signal_children_recursive(int signo, pid_t target_pid) { + DIR *dir; + struct dirent *entry; + char path[256]; + FILE *stat_file; + pid_t ppid; + int count = 0; + + pid_t child_pids[1024]; + int child_count = 0; + + if ((dir = opendir("/proc")) == NULL) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Cannot open /proc directory: %s", strerror(errno)); + return; + } + + while ((entry = readdir(dir)) != NULL) { + pid_t pid; + + if (sscanf(entry->d_name, "%d", &pid) != 1) + continue; + + if (pid == target_pid) + continue; + + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + stat_file = fopen(path, "r"); + if (!stat_file) + continue; + + if (fscanf(stat_file, "%*d %*s %*c %d", &ppid) == 1) { + if (ppid == target_pid && child_count < 1024) { + child_pids[child_count++] = pid; + } + } + fclose(stat_file); + } + + closedir(dir); + + // sort child PIDs in descending order + for (int i = 0; i < child_count - 1; i++) { + for (int j = 0; j < child_count - i - 1; j++) { + if (child_pids[j] < child_pids[j + 1]) { + pid_t temp = child_pids[j]; + child_pids[j] = child_pids[j + 1]; + child_pids[j + 1] = temp; + } + } + } + + // Recursively process child processes in descending order of PID + for (int i = 0; i < child_count; i++) { + pid_t child_pid = child_pids[i]; + + // First, recursively process all child processes of this child + signal_children_recursive(signo, child_pid); + + if (kill(child_pid, signo) == 0) { + count++; + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Send signal %d to child process %d", signo, child_pid); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Failed to send signal %d to process %d: %s", + signo, child_pid, strerror(errno)); + } + } + + if (count > 0) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Sent signal %d to %d child processes of %d", + signo, count, target_pid); + } +} + +static bool signal_gid_except_self(int signo) { + bool success = false; + pid_t self_pid = getpid(); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Sending signal %d to all child processes of %d", + signo, self_pid); + + signal_children_recursive(signo, self_pid); + + if (signo == SIGTERM || signo == SIGKILL) { + + return_code = signo == SIGTERM ? 143 : 137; // 128 + signal number + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Check if some child processes of %d are stopped.", self_pid); + + if (check_process_status(self_pid, SIGSTOP, 1)) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Some child processes of %d are stopped.", self_pid); + // If the process is stopped, we need to send SIGCONT before SIGTERM/SIGKILL + signal_children_recursive(SIGCONT, self_pid); + + success = check_process_status(self_pid, SIGCONT, 5); + if (success) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Sent SIGCONT to all child processes of %d successfully.", self_pid); + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Failed to send SIGCONT to all child processes of %d failed: %s", + self_pid, strerror(errno)); + + signal_children_recursive(SIGKILL, self_pid); + + success = check_process_status(self_pid, SIGKILL, 3); + if (success) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Sent SIGKILL to all child processes of %d successfully.", self_pid); + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Failed to send SIGKILL to all child processes of %d: %s", + self_pid, strerror(errno)); + } + + return_code = 137; // 128 + SIGKILL + return success; + } + + // Send the original signal again + signal_children_recursive(signo, self_pid); + + success = check_process_status(self_pid, signo, 5); + if (success) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Sent %d to all child processes of %d successfully.", + signo, self_pid); + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Failed to send %d to all child processes of %d: %s", + signo, self_pid, strerror(errno)); + } + + return success; + } + } + + int timeout = (signo == SIGKILL) ? 3 : 5; + success = check_process_status(self_pid, signo, timeout); + + if (success) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Successfully sent signal %d to child processes", signo); + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Some child processes may not have responded to signal %d", signo); + } + + return success; +} + +static void signal_term(int signo) { + if (signo == SIGTERM || signo == SIGINT) { + if (reported_flag == 0) { + reported_flag = 1; + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Received signal, terminate job..."); + + if(!signal_gid_except_self(SIGTERM)) { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "SIGTERM may not have been processed by all child processes"); + } + } else { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Received signal again, force terminate job..."); + + if(!signal_gid_except_self(SIGKILL)) { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "SIGKILL may not have been processed by all child processes"); + } + } + } +} + +static void signal_tstp(int signo) { + if (signo == SIGUSR1) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Received signal, pausing job processes..."); + + // Pause the worker process + if(!signal_gid_except_self(SIGSTOP)) { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "SIGSTOP may not have been processed by all child processes"); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "SIGSTOP successfully sent to all child processes"); + + job_runtime_info.job_status = JOB_SUSPENDED; + ipc_worker_send_status(&job_runtime_info, "Suspended"); + } + } +} + +static void signal_cont(int signo) { + if (signo == SIGCONT) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Received signal, resuming job processes..."); + + // Resume the worker process + if(!signal_gid_except_self(SIGCONT)) { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "SIGCONT may not have been processed by all child processes"); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "SIGCONT successfully sent to all child processes"); + + job_runtime_info.job_status = JOB_RUNNING; + ipc_worker_send_status(&job_runtime_info, "Resumed"); + } + } +} + +static void alarm_handler(int sig) { + if (sig == SIGALRM) { + kill(0, SIGKILL); + + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Program running timeout, force exit"); + _exit(1); + } +} + +static void queue_pre_exec() { + int ret; + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Executing queue pre-exec command: %s", + job_runtime_info.q_pre_exec); + + ret = uv_proc_pre_exec(&uv_proc_manager, + job_runtime_info.q_pre_exec, + NULL); + + if (ret != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Queue pre-exec command failed: %s", + uv_strerror(ret)); + + job_runtime_info.exit_code = ret; + job_runtime_info.stage = JOB_EXEC_STAGE_Q_PRE_EXEC; + // TODO: Handle pre-exec failure + exit(1); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Queue pre-exec command completed successfully"); + } +} + +static void queue_post_exec() { + int ret; + job_runtime_info.stage = JOB_EXEC_STAGE_Q_POST_EXEC; + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Executing queue post-exec command: %s", + job_runtime_info.q_post_exec); + + ret = uv_proc_post_exec(&uv_proc_manager, + job_runtime_info.q_post_exec, + NULL); + + if (ret != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Queue post-exec command failed: %s", + uv_strerror(ret)); + job_runtime_info.exit_code = ret; + job_runtime_info.post_done = false; + ipc_worker_send_status(&job_runtime_info, "Queue level post-exec failed"); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Queue post-exec command completed successfully"); + job_runtime_info.post_done = true; + ipc_worker_send_status(&job_runtime_info, "Queue level post-exec completed"); + } +} + +static void job_pre_exec() { + int ret; + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Executing job pre-exec command: %s", + job_runtime_info.job_pre_exec); + + ret = uv_proc_pre_exec(&uv_proc_manager, + job_runtime_info.job_pre_exec, + NULL); + + if (ret != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Job pre-exec command failed: %s", + uv_strerror(ret)); + job_runtime_info.exit_code = ret; + job_runtime_info.stage = JOB_EXEC_STAGE_PRE_EXEC; + // TODO: Handle pre-exec failure + exit(1); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Job pre-exec command completed successfully"); + } +} + +static void job_post_exec() { + int ret; + job_runtime_info.stage = JOB_EXEC_STAGE_POST_EXEC; + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Executing job post-exec command: %s", + job_runtime_info.job_post_exec); + + ret = uv_proc_post_exec(&uv_proc_manager, + job_runtime_info.job_post_exec, + NULL); + + if (ret != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Job post-exec command failed: %s", + uv_strerror(ret)); + job_runtime_info.exit_code = ret; + job_runtime_info.post_done = false; + ipc_worker_send_status(&job_runtime_info, "Job level post-exec failed"); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Job post-exec command completed successfully"); + job_runtime_info.post_done = true; + ipc_worker_send_status(&job_runtime_info, "Job level post-exec completed"); + } +} + +static void process_exit_callback(int pid, int exit_status, + int term_signal, void* user_data) +{ + check_and_reconnect(); + + if (term_signal == 0) { + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Child %d exited with status %d\n", + pid, exit_status); + + job_runtime_info.exit_code = exit_status; + if (exit_status == 0) { + /* normal exit */ + job_runtime_info.job_status = JOB_DONE; + job_runtime_info.worker_status = WORKER_DONE; + job_runtime_info.job_pid = pid; + ipc_worker_send_status(&job_runtime_info, "Completed"); + } else { + ipc_worker_send_status(&job_runtime_info, "Exited"); + return_code = exit_status; + } + + } else if (term_signal > 0) { + /* customizing exit code */ + exit_status = 128 + term_signal; + job_runtime_info.exit_code = exit_status; + job_runtime_info.job_status = JOB_EXITED; + job_runtime_info.worker_status = WORKER_EXITED; + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Child %d killed by signal %d\n", + pid, term_signal); + + ipc_worker_send_status(&job_runtime_info, "Killed"); + return_code = exit_status; + } + + /* Job post-exec start here (blocked) */ + if (XJOB == mydata.transfer_info->interactive_mode) { + xjob_stop_vnc(); + } + + if (job_runtime_info.job_post_exec[0] != '\0') { + job_post_exec(); + } + + if (job_runtime_info.q_post_exec[0] != '\0') { + queue_post_exec(); + } + + /* wait for message successfully sent */ + usleep(500 * 1000); + + worker_exit_flag = 1; + + return; +} + +static void init_xcecd_worker() { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Initializing xcecd worker..."); +} + +static void cleanup_xcecd_worker(void) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Cleaning up xcecd worker..."); + + uv_proc_cleanup(&uv_proc_manager); + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Worker with PID %d exits.", + worker_pid); + + xce_log_fini(); +} + +static void worker_connection_callback(bool connected) { + if (connected) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Connected to xcecd master"); + ipc_worker_request_job(); + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to connect to xcecd master"); + exit(1); + } +} + +static void worker_reconnect_callback(bool connected) { + if (connected) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Re-connected to xcecd master"); + worker_reconnected = 1; + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to connect to xcecd master"); + } +} + +static void check_and_reconnect() { + if (!ipc_worker_check_connection()) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Connection to master lost, retrying..."); + + ipc_worker_reconnect(worker_reconnect_callback); + } else { + return; + } + + while (worker_reconnected == 0) { + + ipc_worker_check_connection(); + + ipc_worker_reconnect(worker_reconnect_callback); + reconnect_retry_limit--; + + if (reconnect_retry_limit <= 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to reconnect to master after multiple attempts"); + exit(1); + } + + sleep(reconnect_retry_interval); + } + + reconnect_retry_limit = 5; +} + +static void xjob_start_vnc() +{ + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Starting VNC server for interactive job %lu", + jobid); + + char vnc_start_cmd[MAXLINELEN] = {0}; + if (access(XJOB_PRE_EXEC, F_OK) != 0) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "VNC pre-exec command not found: %s", + XJOB_PRE_EXEC); + char* server_dir = getenv("XCE_SERVICE_DIR"); + if (server_dir) { + snprintf(vnc_start_cmd, sizeof(vnc_start_cmd), + "%s/startvnc %d", server_dir, port_info.xjob_port); + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "XCE_SERVICE_DIR not set, cannot find VNC pre-exec command"); + exit(1); + } + } else { + snprintf(vnc_start_cmd, sizeof(vnc_start_cmd), + "%s %d", XJOB_PRE_EXEC, port_info.xjob_port); + } + + if (uv_proc_pre_exec_background(&uv_proc_manager, + vnc_start_cmd, + NULL, 1) == -1) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to start VNC server: %s", + vnc_start_cmd); + + exit(1); + } + + if (export_display_env(port_info.xjob_port) == -1) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to export DISPLAY environment variable for VNC server"); + exit(1); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Exported DISPLAY environment variable for VNC server: %d", + port_info.xjob_port); + } + + /* Checking the port find whether server started */ + int timeout_seconds = 10; + int elapsed_time = 0; + + while (check_port_availability(port_info.xjob_port) != 1) { + if (elapsed_time >= timeout_seconds) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Timeout waiting for VNC server to start on port %d after %d seconds", + port_info.xjob_port, timeout_seconds); + exit(1); + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Waiting for VNC server to start on port %d (elapsed: %ds/%ds)", + port_info.xjob_port, elapsed_time, timeout_seconds); + sleep(1); + elapsed_time++; + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "VNC server started for job %lu", + jobid); +} + +static void xjob_stop_vnc() +{ + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Stopping VNC server for interactive job %lu", + jobid); + + char vnc_stop_cmd[MAXLINELEN] = {0}; + if (access(XJOB_POST_EXEC, F_OK) != 0) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "VNC post-exec command not found: %s", + XJOB_POST_EXEC); + char* server_dir = getenv("XCE_SERVICE_DIR"); + if (server_dir) { + snprintf(vnc_stop_cmd, sizeof(vnc_stop_cmd), + "%s/stopvnc %d", server_dir, port_info.xjob_port); + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "XCE_SERVICE_DIR not set, cannot find VNC post-exec command"); + exit(1); + } + } else { + snprintf(vnc_stop_cmd, sizeof(vnc_stop_cmd), + "%s %d", XJOB_POST_EXEC, port_info.xjob_port); + } + + if (uv_proc_post_exec(&uv_proc_manager, + vnc_stop_cmd, + NULL) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to stop VNC server: %s", + vnc_stop_cmd); + + exit(1); + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "VNC server stopped for job %lu", + jobid); +} + +static void worker_job_callback(job_assignment_t* info) +{ + char* job_cmd = NULL; + char* url = NULL; + char* start_exdata_info = NULL; + int rc = 0; + + if (!info) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Received null job assignment"); + exit(1); + } + + if (!info->cmd[0] || strlen(info->cmd) == 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Received empty job command"); + exit(1); + } + + if (info->job_id == 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Received invalid job ID: 0"); + exit(1); + } + + jobid = info->job_id; + uv_proc_manager.job_id = jobid; + job_runtime_info.job_id = jobid; + + if (info->q_pre_exec[0]) { + strncpy(job_runtime_info.q_pre_exec, + info->q_pre_exec, sizeof(job_runtime_info.q_pre_exec) - 1); + } + + if (info->q_post_exec[0]) { + strncpy(job_runtime_info.q_post_exec, + info->q_post_exec, sizeof(job_runtime_info.q_post_exec) - 1); + } + + if (info->job_pre_exec[0]) { + strncpy(job_runtime_info.job_pre_exec, + info->job_pre_exec, sizeof(job_runtime_info.job_pre_exec) - 1); + } + + if (info->job_post_exec[0]) { + strncpy(job_runtime_info.job_post_exec, + info->job_post_exec, sizeof(job_runtime_info.job_post_exec) - 1); + } + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Received job: %s, job_id: %lu, exdata: %s", + info->cmd, jobid, info->exdata[0] ? info->exdata : "no data"); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Job pre-exec: %s", + job_runtime_info.job_pre_exec[0] ? job_runtime_info.job_pre_exec : "no data"); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Job post-exec: %s", + job_runtime_info.job_post_exec[0] ? job_runtime_info.job_post_exec : "no data"); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Queue pre-exec: %s", + job_runtime_info.q_pre_exec[0] ? job_runtime_info.q_pre_exec : "no data"); + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Queue post-exec: %s", + job_runtime_info.q_post_exec[0] ? job_runtime_info.q_post_exec : "no data"); + + if (!parse_exdata(info->exdata, + &mydata.env_vars, + mydata.transfer_info, + jobid)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to parse exdata of job %lu", + jobid); + exit(1); + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Parsed exdata for job %lu: interactive=%d", + jobid, mydata.transfer_info->interactive_mode); + + int job_port = check_port(port_info, + mydata.transfer_info->interactive_mode); + if (job_port < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to check port availability for job %lu", + jobid); + exit(1); + } else if (job_port >5900 && job_port < 10000) { + if (mydata.transfer_info->interactive_mode == TTY || + mydata.transfer_info->interactive_mode == DISPLAY) { + port_info.tty_port = job_port; + } else if (mydata.transfer_info->interactive_mode == XJOB) { + port_info.xjob_port = job_port; + } + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Assigned port %d for job %lu", + job_port, jobid); + } else if (job_port == 0) { + /* normal jobs don't need port info */ + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid port %d for job %lu", + job_port, jobid); + exit(1); + } + + job_cmd = generate_cmd((char*)info->cmd, + port_info, + mydata.transfer_info->interactive_mode); + + /* export XCE environment variables here */ + apply_env_vars(&mydata.env_vars, jobid); + + /* X server start here (blocked) */ + if (XJOB == mydata.transfer_info->interactive_mode) { + xjob_start_vnc(); + } + + /* Queue pre-exec */ + if (job_runtime_info.q_pre_exec[0] != '\0') { + queue_pre_exec(); + } + + /* Job pre-exec */ + if (job_runtime_info.job_pre_exec[0] != '\0') { + job_pre_exec(); + } + + job_runtime_info.stage = JOB_EXEC_STAGE_MAIN; + + /* Real job exec start here (non-block) */ + if (info->ntasks > 1) { + /* Handle parallel task execution */ + rc = uv_proc_exec(&uv_proc_manager, + job_cmd, + NULL, + process_exit_callback, + NULL, + info->ntasks); + } else { + /* Handle single task execution */ + rc = uv_proc_exec_single(&uv_proc_manager, + job_cmd, + NULL, + process_exit_callback, + NULL); + } + + if (rc == 0) { + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Process started successfully to run task: %s", + info->cmd); + + if (mydata.transfer_info->interactive_mode > 0) { + url = generate_url(port_info, + mydata.transfer_info->interactive_mode); + if (url) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "URL for interactive job %lu: %s", + jobid, url); + } + + start_exdata = calloc(1, sizeof(start_exdata_t)); + if (!start_exdata) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for start_exdata_t"); + exit(1); + } + start_exdata->url = url; + + start_exdata_info = generate_exdata(start_exdata); + if (!start_exdata_info) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to generate start_exdata_info for job %lu", + jobid); + exit(1); + } else { + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Generated start_exdata_info for job %lu: %s", + jobid, start_exdata_info); + } + + strncpy(job_runtime_info.exdata, + start_exdata_info, + sizeof(job_runtime_info.exdata) - 1); + job_runtime_info.exdata[sizeof(job_runtime_info.exdata) - 1] = '\0'; + } + + check_and_reconnect(); + + job_runtime_info.job_pid = uv_proc_get_pid(&uv_proc_manager); + job_runtime_info.job_status = JOB_RUNNING; + job_runtime_info.worker_status = WORKER_RUNNING; + + ipc_worker_send_status(&job_runtime_info, "Running"); + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Reported job %lu run status to master.", + jobid); + + FREEUP(job_cmd); + FREEUP(url); + FREEUP(start_exdata); + + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to start process to run task: %s", + info->cmd); + + check_and_reconnect(); + + job_runtime_info.job_pid = 0; + job_runtime_info.job_status = JOB_FAILED; + job_runtime_info.worker_status = WORKER_EXITED; + job_runtime_info.exit_code = 1; + + ipc_worker_send_status(&job_runtime_info, "Failed to start process"); + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Reported job %lu failed status to master.", + jobid); + worker_exit_flag = 1; + return_code = 1; + } + + FREEUP(job_cmd); + ipc_worker_run(UV_RUN_NOWAIT); +} + +static bool check_process_status(pid_t target_pid, + int expected_signal, + int timeout_seconds) +{ + DIR *dir; + struct dirent *entry; + char path[MAXPATHLEN]; + FILE *stat_file; + pid_t ppid; + char buf[MAXLINELEN]; + char state; + int total_children = 0; + time_t start_time = time(NULL); + pid_t *children = NULL; + size_t cap = 0, cnt = 0; + + if ((dir = opendir("/proc")) == NULL) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Cannot open /proc directory: %s", strerror(errno)); + return false; + } + + /* collect all direct child PIDs (initial list) */ + while ((entry = readdir(dir)) != NULL) { + pid_t pid; + if (sscanf(entry->d_name, "%d", &pid) != 1) + continue; + if (pid == target_pid) + continue; + + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + stat_file = fopen(path, "r"); + if (!stat_file) + continue; + + if (fgets(buf, sizeof(buf), stat_file)) { + /* parse pid (comm) state ppid : use pattern that tolerates spaces in comm */ + /* format: pid (comm) state ppid ... */ + int parsed_pid; + char comm[256]; + if (sscanf(buf, "%d (%255[^)]) %c %d", + &parsed_pid, comm, &state, &ppid) == 4) { + if (ppid == target_pid) { + if (cnt + 1 > cap) { + size_t ncap = cap ? cap * 2 : 128; + pid_t *tmp = realloc(children, ncap * sizeof(pid_t)); + if (!tmp) { + fclose(stat_file); + closedir(dir); + FREEUP(children); + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Out of memory collecting children"); + return false; + } + children = tmp; + cap = ncap; + } + children[cnt++] = pid; + } + } + } + fclose(stat_file); + } + closedir(dir); + + total_children = (int)cnt; + if (total_children == 0) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "No child processes found for PID %d", target_pid); + FREEUP(children); + return true; + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Checking status change for %d child processes of PID %d (signal: %d)", + total_children, target_pid, expected_signal); + + while ((time(NULL) - start_time) < timeout_seconds) { + int changed_count = 0; + + for (size_t i = 0; i < cnt; i++) { + pid_t pid = children[i]; + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + stat_file = fopen(path, "r"); + if (!stat_file) { + /* process no longer exists */ + if (expected_signal == SIGTERM || expected_signal == SIGKILL) { + changed_count++; + } + continue; + } + + if (fgets(buf, sizeof(buf), stat_file)) { + int parsed_pid; + char comm[256]; + if (sscanf(buf, "%d (%255[^)]) %c %d", + &parsed_pid, comm, &state, &ppid) == 4) { + if (ppid != target_pid) { + /* process is no longer a child of the target */ + changed_count++; + } else { + /* determine status change based on expected signal */ + if (expected_signal == SIGSTOP || + expected_signal == SIGTSTP) { + if (state == 'T') changed_count++; + } else if (expected_signal == SIGCONT) { + if (state != 'T') changed_count++; + } else if (expected_signal == SIGKILL || + expected_signal == SIGTERM) { + /* treat process disappearance or zombie (Z) as response for TERM/KILL */ + if (state == 'Z') changed_count++; + } else { + /* for other signals, any non-running state change is recorded (conservative) */ + if (state != 'R' && state != 'S') changed_count++; + } + } + } else { + /* unable to parse, skip */ + } + } + fclose(stat_file); + } + + if (changed_count >= total_children) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "All %d child processes of PID %d changed status successfully", + total_children, target_pid); + FREEUP(children); + return true; + } + + if (expected_signal == SIGKILL) { + usleep(100000); // 0.1s + } else { + usleep(500000); // 0.5s + } + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Status change check timeout: %d/%d child processes changed status for PID %d (signal: %d)", + 0, total_children, target_pid, expected_signal); + + FREEUP(children); + return false; +} + +/** + * @brief worker signal callback to handle job control signals + * @param job_id - job ID + * @param signal - signal received + * @param force - whether signal is forced + * @return void + */ +static void worker_signal_callback(uint64_t job_id, + int signal, + bool force) +{ + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Received signal %d for job %lu, force=%d", + signal, job_id, force); + + if (job_id != jobid) { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Signal for job %lu but current job is %lu", + job_id, jobid); + return; + } + + pid_t target_pid = uv_proc_get_pid(&uv_proc_manager); + if (target_pid <= 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "No active process to send signal to"); + return; + } + + bool success = false; + + switch (signal) { + case SIGTERM: + case SIGKILL: + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Terminating job %lu with signal %d", + job_id, signal); + if (force) { + success = signal_gid_except_self(SIGKILL); + } else { + success = signal_gid_except_self(SIGTERM); + } + break; + + case SIGSTOP: + case SIGTSTP: + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Pausing job %lu", + job_id); + success = signal_gid_except_self(SIGSTOP); + if (success) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Job %lu paused successfully", + job_id); + + job_runtime_info.job_status = JOB_SUSPENDED; + ipc_worker_send_status(&job_runtime_info, "Suspended"); + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Failed to pause job %lu", + job_id); + } + break; + + case SIGCONT: + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Resuming job %lu", + job_id); + success = signal_gid_except_self(SIGCONT); + if (success) { + XCECD_WORKER_LOG(XCE_LOG_DEBUG2, + "Job %lu resumed successfully", + job_id); + + job_runtime_info.job_status = JOB_RUNNING; + ipc_worker_send_status(&job_runtime_info, "Resumed"); + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Failed to resume job %lu", + job_id); + } + break; + + default: + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Sending signal %d for job %lu", + signal, job_id); + success = signal_gid_except_self(signal); + break; + } +} + +static char* criu_path() { + char launcher[MAXPATHLEN] = {0}; + char exe_path[MAXPATHLEN - 5] = {0}; + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len > 0) { + exe_path[len] = '\0'; + char* last_slash = strrchr(exe_path, '/'); + if (last_slash) { + *last_slash = '\0'; + snprintf(launcher, sizeof(launcher), "%s/criu", exe_path); + } else { + snprintf(launcher, sizeof(launcher), "/opt/xce/bin/criu"); + } + } else { + snprintf(launcher, sizeof(launcher), "/opt/xce/bin/criu"); + } + + if (access(launcher, X_OK) == 0) { + return strdup(launcher); + } + + return NULL; + +} + +static void do_criu_ckpt(uint64_t job_id, int kill) { + pid_t target_pid = 0; + + char *path = criu_path(); + if (!path) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "CRIU is not available for checkpointing job %lu", + job_id); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + + return; + } + + char *ckpt_pid_str = getenv("XCE_JOB_CHECKPOINT_PID"); + if (!ckpt_pid_str) { + target_pid = uv_proc_get_pid(&uv_proc_manager); + if (target_pid <= 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "No active process to checkpoint"); + FREEUP(path); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + return; + } + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Using current process PID %d for checkpointing", + target_pid); + } else { + target_pid = atoi(ckpt_pid_str); + if (target_pid <= 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Invalid checkpoint PID: %s", + ckpt_pid_str); + FREEUP(path); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + return; + } + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Using specified checkpoint PID %d", + target_pid); + } + + char ckpt_dir[MAXPATHLEN]; + snprintf(ckpt_dir, sizeof(ckpt_dir), "%s/%lu", CKPT_BASE, job_id); + + if (mkdir(ckpt_dir, 0755) != 0 && errno != EEXIST) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to create checkpoint directory %s: %s", + ckpt_dir, strerror(errno)); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + FREEUP(path); + return; + } + + if (access(ckpt_dir, W_OK) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Cannot write to checkpoint directory %s", + ckpt_dir); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + FREEUP(path); + return; + } + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "Checkpoint directory created: %s", + ckpt_dir); + + char criu_cmd[MAXLINELEN]; + char info[MAXLINELEN]; + if (kill) { + snprintf(criu_cmd, sizeof(criu_cmd), + "cd %s; %s dump -t %d -vvv -o dump.log", ckpt_dir, path, target_pid); + } else { + snprintf(criu_cmd, sizeof(criu_cmd), + "cd %s; %s dump -t %d -R -vvv -o dump.log", ckpt_dir, path, target_pid); + } + + int ret = system(criu_cmd); + if (ret != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to run criu command: %s", + criu_cmd); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + FREEUP(path); + return; + } + +#if 0 + // Check if the dump was successful + char dump_log[MAXLINELEN]; + snprintf(dump_log, sizeof(dump_log), "%s/dump.log", ckpt_dir); + FILE *log_file = fopen(dump_log, "r"); + if (!log_file) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to open dump log file: %s", + dump_log); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + FREEUP(path); + return; + } + + // Check for errors in the log file + char line[MAXLINELEN]; + bool has_error = false; + while (fgets(line, sizeof(line), log_file)) { + if (strstr(line, "ERROR") || strstr(line, "FAILED")) { + has_error = true; + break; + } + } + fclose(log_file); + if (has_error) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "CRIU dump failed for job %lu, check %s for details", + job_id, dump_log); + ipc_worker_send_ckpt_result(job_id, 1, NULL); + FREEUP(path); + return; + } +#endif + + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "CRIU command executed successfully: %s", + criu_cmd); + + snprintf(info, sizeof(info), + "{\"ckpt_pid\": %d, \"restart_command\": \"cd %s; %s restore -vvv -o restore.log\"}", + target_pid, ckpt_dir, path); + + ipc_worker_send_ckpt_result(job_id, 0, info); + + FREEUP(path); +} + +static void worker_ckpt_callback(uint64_t job_id, + int kill, + const char* method) +{ + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Checkpointing job %lu", + job_id); + + job_runtime_info.job_id = job_id; + + if (strcasecmp(method, "default") || strcasecmp(method, "criu")) { + do_criu_ckpt(job_id, kill); + } else { + XCECD_WORKER_LOG(XCE_LOG_WARN, + "Currently XCE only support criu checkpointing."); + } + + XCECD_WORKER_LOG(XCE_LOG_DEBUG, + "Reported job %lu checkpoint status to master.", + job_id); +} + +int main(int argc, char* argv[]) { + int opt; + int log_level = XCE_LOG_INFO; + + port_info.tty_port = 9000; + port_info.xjob_port = 5901; + + mydata.transfer_info = calloc(1, sizeof(transfer_info_t)); + if (!mydata.transfer_info) { + fprintf(stderr, "Failed to allocate memory for transfer info"); + exit(1); + } + + signal(SIGPIPE, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGSTOP, SIG_IGN); + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_term; + sa.sa_flags = SA_RESTART; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + struct sigaction sa_alrm; + memset(&sa_alrm, 0, sizeof(sa_alrm)); + sa_alrm.sa_handler = alarm_handler; + sigaction(SIGALRM, &sa_alrm, NULL); + + struct sigaction sa_tstp; + memset(&sa_tstp, 0, sizeof(sa_tstp)); + sa_tstp.sa_handler = signal_tstp; + sa_tstp.sa_flags = SA_NODEFER; + sigaction(SIGSTOP, &sa_tstp, NULL); + + struct sigaction sa_cont; + memset(&sa_cont, 0, sizeof(sa_cont)); + sa_cont.sa_handler = signal_cont; + sa_cont.sa_flags = SA_RESETHAND | SA_RESTART; + sigaction(SIGCONT, &sa_cont, NULL); + + while ((opt = getopt(argc, argv, "d:l:h")) != -1) { + switch (opt) { + case 'l': + if (optarg) { + char filename[1024]; + snprintf(filename, sizeof(filename), + "%s/xcecd_worker.log", optarg); + log_file = strdup(filename); + } else { + fprintf(stderr, "Log file path is required.\n"); + exit(1); + } + break; + case 'd': + if (optarg) { + log_level = atoi(optarg); + } else { + fprintf(stderr, "Log level is required.\n"); + exit(1); + } + break; + case 'h': + default: + // Print usage information + fprintf(stderr, "Usage: %s -l -d \n", argv[0]); + exit(0); + } + } + + if (!log_file) { + log_file = strdup(LOG_FILE); + } + + worker_pid = getppid(); + if (worker_pid == 1) { + /* If the worker is a child of init, use the current process ID */ + worker_pid = getpid(); + } + + job_runtime_info.worker_pid = worker_pid; + + if (xce_init_xcecdwk_log(log_file, LOG_SIZE, log_level) != 0) + { + /* FIXME: write error message to a default log file. */ + fprintf(stderr, "Failed to initialize log module."); + exit(1); + } + + init_xcecd_worker(); + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Worker process started with pid: %d", + worker_pid); + + if (!ipc_worker_init(SOCKET_PATH)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to initialize xcecd worker"); + cleanup_xcecd_worker(); + _exit(1); + } + + ipc_worker_set_job_callback(worker_job_callback); + ipc_worker_connect(worker_connection_callback); + ipc_worker_set_reconnection_callback(worker_reconnect_callback); + ipc_worker_set_signal_callback(worker_signal_callback); + ipc_worker_set_ckpt_callback(worker_ckpt_callback); + + uv_loop = ipc_get_event_loop(); + if (!uv_loop) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to get event loop"); + cleanup_xcecd_worker(); + _exit(1); + } + + if (uv_proc_init(&uv_proc_manager, uv_loop) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "Failed to initialize process manager"); + cleanup_xcecd_worker(); + _exit(1); + } + + do { + + ipc_worker_run(UV_RUN_NOWAIT); + + usleep(1000); + + } while (worker_exit_flag == 0); + + + ipc_worker_stop(); + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Exited main loop."); + + alarm(5); + + cleanup_xcecd_worker(); + + free_transfer_info(mydata.transfer_info); + FREEUP(log_file); + + exit(return_code); +} diff --git a/src/worker/src/xcecd_worker_utils.c b/src/worker/src/xcecd_worker_utils.c new file mode 100644 index 00000000..9909db25 --- /dev/null +++ b/src/worker/src/xcecd_worker_utils.c @@ -0,0 +1,552 @@ +#include "xcecd_worker_utils.h" +#include "xcecd_dbus_manager.h" +#include "xcecd_utils.h" +#include "uv_ipc.h" +#include "xcelog.h" +#include +#include +#include +#include +#include + + + +void add_worker(xcecd_worker_t** worker_list, + int worker_pid, + int worker_status, + uint64_t job_id, + int ntasks, + job_startinfo_t* startinfo) +{ + + xcecd_worker_t* worker = (xcecd_worker_t*)calloc(1, sizeof(xcecd_worker_t)); + if (!worker) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to allocate memory for worker"); + return; + } + + XCECD_LOG(XCE_LOG_DEBUG, + "Adding worker %d to worker list", + worker_pid); + + worker->worker_pid = worker_pid; + worker->worker_status = worker_status; + worker->job_info.jobid = job_id; + worker->job_info.pidinfo.npids = 0; + worker->job_info.pidinfo.pids = NULL; + worker->job_info.pidinfo.npgids = 0; + worker->job_info.pidinfo.pgids = NULL; + worker->job_status = JOB_STARTING; + worker->ntasks = ntasks; + + if (!startinfo) { + XCECD_LOG(XCE_LOG_WARN, + "No startinfo provided for worker %d", + worker_pid); + // This only happens for recovery jobs + goto add_worker_no_startinfo; + } + + if (startinfo->launch_type) { + worker->launch_type = strndup(startinfo->launch_type, strlen(startinfo->launch_type)); + } else { + worker->launch_type = NULL; + XCECD_LOG(XCE_LOG_DEBUG, + "No launch_type provided, it is serial job."); + } + + if (startinfo->exechome) { + worker->exechome = strndup(startinfo->exechome, strlen(startinfo->exechome)); + } else { + worker->exechome = NULL; + } + + if (startinfo->execcwd) { + worker->execcwd = strndup(startinfo->execcwd, strlen(startinfo->execcwd)); + } else { + worker->execcwd = NULL; + } + + if (startinfo->execuser) { + worker->execuser = strndup(startinfo->execuser, strlen(startinfo->execuser)); + } else { + worker->execuser = strdup("nobody"); + XCECD_LOG(XCE_LOG_WARN, + "No execuser provided, using default 'nobody'"); + } + + if (startinfo->cmdline) { + worker->job_cmd = strndup(startinfo->cmdline, strlen(startinfo->cmdline)); + } else { + worker->job_cmd = NULL; + } + + if (startinfo->exdata) { + worker->job_exdata = strndup(startinfo->exdata, strlen(startinfo->exdata)); + } else { + worker->job_exdata = NULL; + } + + if (startinfo->q_pre_exec) { + worker->q_pre_exec = strndup(startinfo->q_pre_exec, strlen(startinfo->q_pre_exec)); + } else { + worker->q_pre_exec = NULL; + } + + if (startinfo->q_post_exec) { + worker->q_post_exec = strndup(startinfo->q_post_exec, strlen(startinfo->q_post_exec)); + } else { + worker->q_post_exec = NULL; + } + + if (startinfo->job_pre_exec) { + worker->job_pre_exec = strndup(startinfo->job_pre_exec, strlen(startinfo->job_pre_exec)); + } else { + worker->job_pre_exec = NULL; + } + + if (startinfo->job_post_exec) { + worker->job_post_exec = strndup(startinfo->job_post_exec, strlen(startinfo->job_post_exec)); + } else { + worker->job_post_exec = NULL; + } + +#ifdef VIRTUAL_CLUSTER + worker->killed_signal = 0; + worker->start_time = time(NULL); + + if (startinfo->cmdline) { + int sleep_time = 0; + + if (strncmp(startinfo->cmdline, "sleep ", 6) == 0) { + if (sscanf(startinfo->cmdline + 6, "%d", &sleep_time) == 1) { + + if (sleep_time < 0) { + XCECD_LOG(XCE_LOG_ERROR, + "Invalid sleep time %d for worker %d", + sleep_time, + worker_pid); + worker->sleep_time = 0; + } else { + worker->sleep_time = sleep_time; + XCECD_LOG(XCE_LOG_DEBUG3, + "Worker %d will sleep for %d seconds", + worker_pid, + sleep_time); + } + + } else { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to parse sleep time from command: %s", + startinfo->cmdline); + worker->sleep_time = 0; + } + } else { + XCECD_LOG(XCE_LOG_DEBUG3, + "Worker %d command does not contain sleep", + worker_pid); + worker->sleep_time = 0; + } + } else { + XCECD_LOG(XCE_LOG_DEBUG3, + "Worker %d command is NULL", + worker_pid); + worker->sleep_time = 0; + } + +#endif + +add_worker_no_startinfo: + + if (*worker_list == NULL) { + *worker_list = worker; + } else { + xcecd_worker_t* current = *worker_list; + while (current->next != NULL) { + current = current->next; + } + current->next = worker; + worker->prev = current; + } +} + +void remove_worker(xcecd_worker_t** worker_list, + int worker_pid) +{ + + XCECD_LOG(XCE_LOG_DEBUG, + "Removing worker %d from worker list", + worker_pid); + + xcecd_worker_t* worker = *worker_list; + + while (worker) { + if (worker->worker_pid == worker_pid) { + if (worker->prev) { + worker->prev->next = worker->next; + } else { + *worker_list = worker->next; + } + if (worker->next) { + worker->next->prev = worker->prev; + } + + FREEUP(worker->exechome); + FREEUP(worker->execcwd); + FREEUP(worker->execuser); + FREEUP(worker->job_cmd); + FREEUP(worker->job_info.pidinfo.pids); + FREEUP(worker->job_exdata); + FREEUP(worker); + break; + } + worker = worker->next; + } +} + +int update_worker_status(xcecd_worker_t* worker_list, + int worker_pid, + int worker_status, + xcecd_job_t* job, + sqlite3* db) +{ + int ret = XCECD_OK; + + if (!worker_list) { + XCECD_LOG(XCE_LOG_DEBUG, "Worker list is empty"); + return -1; + } + + block_signal(SIGTERM); + block_signal(SIGINT); + + xcecd_worker_t* worker = worker_list; + while (worker) { + if (worker->worker_pid == worker_pid) { + if (worker->worker_status == WORKER_EXITED || + worker->worker_status == WORKER_DONE) { + unblock_signal(SIGTERM); + unblock_signal(SIGINT); + return ret; + } + + if ((NULL == worker->job_info.pidinfo.pids) && + (0 != job->job_pid)) { + worker->job_info.pidinfo.npids = 1; + worker->job_info.pidinfo.pids = (unsigned int*)calloc( + worker->job_info.pidinfo.npids, + sizeof(unsigned int)); + worker->job_info.pidinfo.pids[0] = (unsigned int)job->job_pid; + } + job->job_id = worker->job_info.jobid; + + if (job->job_status == JOB_DONE || + job->job_status == JOB_EXITED) { + worker->job_status = job->job_status; + } + + switch (worker_status) + { + case WORKER_RUNNING: + if ((worker->job_status == JOB_STARTING) && + (job->job_status == JOB_RUNNING)) { + XCECD_LOG(XCE_LOG_DEBUG, + "Job ID %lu running - sending status to controller", + job->job_id); + worker->job_status = JOB_RUNNING; + xcecd_reply_startjob(job); + + if (db && + !xcecd_db_update_worker(db, + worker->worker_pid, + WORKER_RUNNING, + JOB_RUNNING)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + ret = XCECD_DB_ERROR; + } + } else if (job->job_status == JOB_SUSPENDED) { + // Send controller that job is stopped + XCECD_LOG(XCE_LOG_DEBUG, + "Job %lu is suspended.", + job->job_id); + + worker->job_status = JOB_SUSPENDED; + xcecd_job_stopped(job->job_id); + + if (db && + !xcecd_db_update_worker(db, + worker->worker_pid, + WORKER_RUNNING, + JOB_SUSPENDED)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + ret = XCECD_DB_ERROR; + } + } else if (worker->job_status == JOB_SUSPENDED && + job->job_status == JOB_RUNNING) { + XCECD_LOG(XCE_LOG_DEBUG, + "Job %lu is resumed.", + job->job_id); + worker->job_status = JOB_RUNNING; + xcecd_job_resumed(job->job_id); + + if (db && + !xcecd_db_update_worker(db, + worker->worker_pid, + WORKER_RUNNING, + JOB_RUNNING)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + ret = XCECD_DB_ERROR; + } + } else if (worker->job_status == JOB_RUNNING) { + unblock_signal(SIGTERM); + unblock_signal(SIGINT); + return ret; + } + break; + + case WORKER_DONE: + worker->worker_status = WORKER_DONE; + worker->job_status = JOB_DONE; + XCECD_LOG(XCE_LOG_DEBUG, + "Job %lu done and sending status to controller", + job->job_id); + xcecd_job_finished(job); + /* FIXME: add error handling when msg send failure */ + + if (db && + !xcecd_db_update_worker(db, + worker->worker_pid, + WORKER_DONE, + JOB_DONE)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + ret = XCECD_DB_ERROR; + } + + dbus_worker_clean(worker, job); + break; + + case WORKER_EXITED: + worker->worker_status = WORKER_EXITED; + worker->job_status = JOB_EXITED; + XCECD_LOG(XCE_LOG_DEBUG, + "Job %lu exited and sending status to controller", + job->job_id); + xcecd_job_finished(job); + + if (db && + !xcecd_db_update_worker(db, + worker->worker_pid, + WORKER_EXITED, + JOB_EXITED)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + ret = XCECD_DB_ERROR; + } + + dbus_worker_clean(worker, job); + break; + + case WORKER_UNKNOWN: + worker->worker_status = WORKER_UNKNOWN; + worker->job_status = JOB_EXITED; + XCECD_LOG(XCE_LOG_DEBUG, + "Job %lu status is unknown and sending status to controller", + job->job_id); + xcecd_job_finished(job); + /* FIXME: we don't have unknown status currently */ + + if (db && + !xcecd_db_update_worker(db, + worker->worker_pid, + WORKER_UNKNOWN, + JOB_EXITED)) + { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to update worker status in database"); + ret = XCECD_DB_ERROR; + } + + dbus_worker_clean(worker, job); + break; + + default: + /* something wrong */ + worker->job_status = JOB_UNKNOWN; + break; + } + + if (worker->worker_status != worker_status) { + worker->worker_status = worker_status; + XCECD_LOG(XCE_LOG_DEBUG, + "Updated worker %d status to %d", + worker_pid, + worker_status); + } + + if (!ipc_master_check_connection()) { + XCECD_LOG(XCE_LOG_FATAL, + "IPC connection is broken, exit."); + exit(1); + } + + unblock_signal(SIGTERM); + unblock_signal(SIGINT); + + return ret; + } + + worker = worker->next; + } + + unblock_signal(SIGTERM); + unblock_signal(SIGINT); + + return XCECD_NOT_FOUND; +} + +void cleanup_worker_list(xcecd_worker_t** worker_list, + sqlite3* db) +{ + if (!worker_list || !*worker_list) { + return; + } + //XCECD_LOG(XCE_LOG_DEBUG, "Cleaning up worker list"); + + xcecd_worker_t* worker = *worker_list; + while (worker) { + xcecd_worker_t* next = worker->next; + + if (worker->worker_status == WORKER_EXITED || + worker->worker_status == WORKER_DONE) { + if (!xcecd_db_delete_worker(db, + worker->worker_pid)) { + XCECD_LOG(XCE_LOG_ERROR, + "Failed to delete worker from database"); + } + + remove_worker(worker_list, worker->worker_pid); + } + + worker = next; + } +} + +xcecd_worker_t* find_worker_by_pid(void* worker_list, + int pid) +{ + XCECD_LOG(XCE_LOG_DEBUG, + "Finding worker %d in worker list", + pid); + + xcecd_worker_t* worker = (xcecd_worker_t*)worker_list; + while (worker) { + if (worker->worker_pid == pid) { + return worker; + } + + worker = worker->next; + } + + return NULL; +} + +xcecd_worker_t* find_worker_by_jobid(xcecd_worker_t* worker_list, + uint64_t jobid) +{ + XCECD_LOG(XCE_LOG_DEBUG, + "Finding worker with job ID %lu in worker list", + jobid); + + xcecd_worker_t* worker = worker_list; + while (worker) { + if (worker->job_info.jobid == jobid) { + return worker; + } + + worker = worker->next; + } + + return NULL; +} + +/** + * @brief Resend job status to controller when recovering from failure + * @param worker_list Pointer to the worker list + * @return true if success, false if failed + */ +bool resend_job_status(xcecd_worker_t* worker_list) { + /* no need to sync when worker list is empty */ + if (!worker_list) { + return true; + } + + xcecd_job_t job = { + .job_id = 0, + .job_pid = 0, + .job_status = JOB_RUNNING, + .exit_code = 0 + }; + + xcecd_worker_t* worker = worker_list; + while (worker) { + if (worker->worker_status == WORKER_RUNNING) { + XCECD_LOG(XCE_LOG_DEBUG, + "Resending job %lu status to controller.", + worker->job_info.jobid); + + job.job_id = worker->job_info.jobid; + xcecd_reply_startjob(&job); + } + worker = worker->next; + } + + return true; +} + +#ifdef VIRTUAL_CLUSTER +/** + * @brief Checks if the time elapsed since the worker's last event timestamp + * has reached or exceeded its specified sleep duration. + * + * @param worker Pointer to the worker structure. + * @return true if the sleep time has elapsed or if worker/sleep_duration is invalid, + * false otherwise. + */ +bool check_worker_sleep_time_elapsed(xcecd_worker_t* worker) { + if (!worker) { + return true; + } + + if (worker->sleep_time <= 0) { + return true; + } + + time_t current_time = time(NULL); + if (current_time == (time_t)-1) { + XCECD_LOG(XCE_LOG_ERROR, "Failed to get current time."); + return true; + } + + double time_diff = difftime(current_time, worker->start_time); + + if (time_diff >= worker->sleep_time) { + XCECD_LOG(XCE_LOG_DEBUG, "Sleep time %.0f >= %d seconds elapsed for worker %d (job %lu)", + time_diff, worker->sleep_time, worker->worker_pid, worker->job_info.jobid); + return true; + } else { + // XCECD_LOG(XCE_LOG_TRACE, "Sleep time remaining for worker %d: %.0f seconds", + // worker->worker_pid, worker->start_time - time_diff); + return false; + } +} +#endif \ No newline at end of file diff --git a/src/worker/src/xrun.c b/src/worker/src/xrun.c new file mode 100644 index 00000000..c6b47a84 --- /dev/null +++ b/src/worker/src/xrun.c @@ -0,0 +1,218 @@ +#include "xrun.h" +#include "xcelog.h" +#include "xce/types.h" +#include "uv_ipc.h" + + + +#define XRUN_LOG "/tmp/xrun.log" +#define XCECD_SOCKET_PATH "/var/run/.xcecd.sock" +// xrun will sleep for 15 seconds to wait connection successfully, then quit +#define XRUN_SLEEP_TIME 15 + +volatile sig_atomic_t running = 1; +int return_code = 0; + +void handle_signal(int sig) { + if (sig == SIGTERM || sig == SIGINT) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Receive signal %d, prepare to exit...\n", sig); + running = 0; + } +} + +void setup_signal_handlers(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handle_signal; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + signal(SIGPIPE, SIG_IGN); +} + +int xrun_ipc_send_remote_start(const char *hostname, const char *command) { + int sock_fd = -1; + struct sockaddr_un serv_addr; + + if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "xrun_ipc: socket() failed: %s\n", strerror(errno)); + return -1; + } + + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sun_family = AF_UNIX; + strncpy(serv_addr.sun_path, XCECD_SOCKET_PATH, sizeof(serv_addr.sun_path) - 1); + + if (connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "xrun_ipc: connect() failed: %s. Is the xcecd worker process running?\n", + strerror(errno)); + close(sock_fd); + return -1; + } + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "xrun_ipc: connected to xcecd at %s", XCECD_SOCKET_PATH); + + ipc_message_t request_msg; + memset(&request_msg, 0, sizeof(request_msg)); + request_msg.type = MSG_REMOTE_START_JOB; + + strncpy(request_msg.data.remote_start.hostname, + hostname, sizeof(request_msg.data.remote_start.hostname) - 1); + strncpy(request_msg.data.remote_start.command, + command, sizeof(request_msg.data.remote_start.command) - 1); + + char* jobid_str = getenv("XCE_JOBID"); + if (jobid_str) { + request_msg.data.remote_start.jobid = strtoull(jobid_str, NULL, 10); + } + + if (send(sock_fd, &request_msg, sizeof(request_msg), 0) < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "xrun_ipc: send() failed: %s\n", strerror(errno)); + close(sock_fd); + return -1; + } + XCECD_WORKER_LOG(XCE_LOG_DEBUG3, + "xrun_ipc: sent remote start request for host '%s'", hostname); + + ipc_message_t response_msg; + ssize_t nread = read(sock_fd, &response_msg, sizeof(response_msg)); + close(sock_fd); + + if (nread < 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "xrun_ipc: read() failed: %s\n", strerror(errno)); + return -1; + } + + if (nread < sizeof(ipc_message_t)) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "xrun_ipc: received incomplete response from xcecd."); + return -1; + } + + if (response_msg.type == MSG_REMOTE_START_JOB_ACK) { + if (response_msg.data.remote_start_ack.success) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "xrun: Remote job request sent to xcecd successfully."); + return 0; + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "xrun: xcecd failed to start remote job: %s", + response_msg.data.remote_start_ack.error_message); + return -1; + } + } else { + XCECD_WORKER_LOG(XCE_LOG_ERROR, + "xrun: received unexpected message type (%d) from xcecd.", + response_msg.type); + return -1; + } + + return 0; +} + +int run_remote_command(const char *hostname, const char *command) { + XCECD_WORKER_LOG(XCE_LOG_INFO, + "Preparing to execute %s on host %s", + command, hostname); + + if ((return_code = xrun_ipc_send_remote_start(hostname, command)) != 0) { + XCECD_WORKER_LOG(XCE_LOG_ERROR, "Failed sending remote start request to xcecd."); + return return_code; + } + +#if 0 + // Wait for signals or the main process command to terminate + while (running) { + sleep(1); + } +#endif + + sleep(XRUN_SLEEP_TIME); + + XCECD_WORKER_LOG(XCE_LOG_INFO, "xrun tool execution completed, exiting normally"); + return 0; +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, "Usage: %s [options] remote_host command\n", argv[0]); + return 1; + } + + char *hostname = NULL; + int cmd_start_index = -1; + + // pattern:-x hostname command + for (int i = 1; i < argc - 1; i++) { + if (strcmp(argv[i], "-x") == 0 && i + 1 < argc) { + hostname = argv[i + 1]; + cmd_start_index = i + 2; + break; + } + } + + if (hostname == NULL) { + for (int i = 1; i < argc - 1; i++) { + if (argv[i][0] != '-') { + hostname = argv[i]; + cmd_start_index = i + 1; + break; + } + } + } + + if (hostname == NULL || cmd_start_index >= argc) { + fprintf(stderr, "Error: Missing hostname or command.\n"); + fprintf(stderr, "Usage: %s [options] remote_host command\n", argv[0]); + return 1; + } + + char full_command[MAXLINELEN] = {0}; + for (int i = cmd_start_index; i < argc; i++) { + if (i > cmd_start_index) { + strcat(full_command, " "); + } + + if (strchr(argv[i], ' ') != NULL && + strchr(argv[i], ';') == NULL && + strchr(argv[i], '|') == NULL && + strchr(argv[i], '&') == NULL && + strchr(argv[i], '$') == NULL && + strchr(argv[i], '`') == NULL && + strchr(argv[i], '=') == NULL && + argv[i][0] != '"' && argv[i][0] != '\'') { + strcat(full_command, "\""); + strcat(full_command, argv[i]); + strcat(full_command, "\""); + } else { + strcat(full_command, argv[i]); + } + } + + if (full_command[0] == '\0') { + fprintf(stderr, "Error: Missing command after hostname.\n"); + fprintf(stderr, "Usage: %s [options] remote_host command\n", argv[0]); + return 1; + } + + setup_signal_handlers(); + + if (xce_init_xcecdwk_log(XRUN_LOG, 1024*1024, XCE_LOG_DEBUG) != 0) { + fprintf(stderr, "Failed to initialize logging module\n"); + return_code = 1; + return return_code; + } + + XCECD_WORKER_LOG(XCE_LOG_INFO, + "xrun tool started with hostname: %s, command: %s", + hostname, full_command); + + return_code = run_remote_command(hostname, full_command); + + return return_code; +} \ No newline at end of file diff --git a/src/worker/test/limit_test.c b/src/worker/test/limit_test.c new file mode 100644 index 00000000..a9624b17 --- /dev/null +++ b/src/worker/test/limit_test.c @@ -0,0 +1,756 @@ +/** + * @file test_xcecd_exdata.c + * @brief Test program for resource limits in xcecd worker + * + * This program tests resource limits by gradually increasing resource usage + * and reporting progress. It can be used to test limits parsing functionality + * in xcecd_exdata.c. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Default values +#define DEFAULT_STEP_SIZE_KB 1024 // Default memory step size (1MB) +#define DEFAULT_STEP_INTERVAL_MS 100 // 100ms between steps +#define DEFAULT_FILE_PATH "/tmp/xcecd_test_file" +#define MAX_THREADS 10000 + +// Global variables for cleanup +volatile sig_atomic_t terminate = 0; +void **memory_blocks = NULL; +int num_memory_blocks = 0; +pthread_t *thread_list = NULL; +int num_threads = 0; +FILE *test_file = NULL; +char *file_path = NULL; +pid_t *process_list = NULL; +int num_processes = 0; + +// Thread function prototype +void *thread_function(void *arg); +void process_function(void); + +// Signal handler +void signal_handler(int sig) { + terminate = 1; + printf("\nReceived signal %d, cleaning up and exiting...\n", sig); +} + +// Function to get current timestamp in milliseconds +long long current_time_ms() { + struct timeval tv; + gettimeofday(&tv, NULL); + return (long long)(tv.tv_sec) * 1000 + (tv.tv_usec / 1000); +} + +// Helper function to print a specific resource limit +static void print_limit(const char *name, int resource) { + struct rlimit rlim; + if (getrlimit(resource, &rlim) == 0) { + printf(" - %-25s: ", name); + if (rlim.rlim_cur == RLIM_INFINITY) { + printf("soft (unlimited), "); + } else { + printf("soft (%-10lld), ", (long long)rlim.rlim_cur); + } + if (rlim.rlim_max == RLIM_INFINITY) { + printf("hard (unlimited)\n"); + } else { + printf("hard (%lld)\n", (long long)rlim.rlim_max); + } + } else { + printf(" - %-25s: Failed to get limit (%s)\n", name, strerror(errno)); + } +} + +// Helper function to print all relevant limits +static void print_all_limits() { + printf("System Resource Limits:\n"); + print_limit("CPU time (seconds)", RLIMIT_CPU); + print_limit("Max file size (bytes)", RLIMIT_FSIZE); + print_limit("Max data segment (bytes)", RLIMIT_DATA); + print_limit("Max stack size (bytes)", RLIMIT_STACK); + print_limit("Max core file size (bytes)", RLIMIT_CORE); + print_limit("Max resident set (bytes)", RLIMIT_RSS); + print_limit("Max processes", RLIMIT_NPROC); + print_limit("Max open files", RLIMIT_NOFILE); + print_limit("Max locked memory (bytes)", RLIMIT_MEMLOCK); + print_limit("Max address space (bytes)", RLIMIT_AS); + printf("\n"); +} + +// Function to print current resource usage +void print_resource_usage() { + // Print limits only on the first call + static int first_call = 1; + if (first_call) { + print_all_limits(); + first_call = 0; + } + + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) == 0) { + printf("Resource usage - User CPU: %.2fs, System CPU: %.2fs, RSS: %ld KB\n", + usage.ru_utime.tv_sec + usage.ru_utime.tv_usec / 1000000.0, + usage.ru_stime.tv_sec + usage.ru_stime.tv_usec / 1000000.0, + usage.ru_maxrss); + } +} + +// Memory test function +void test_memory(size_t target_mb, size_t step_kb, int interval_ms) { + printf("Starting memory test: Target=%zu MB, Step=%zu KB, Interval=%d ms\n", + target_mb, step_kb, interval_ms); + + size_t target_kb = target_mb * 1024; + size_t allocated_kb = 0; + long long start_time = current_time_ms(); + long long next_step_time = start_time; + + // Pre-allocate array for memory block pointers + num_memory_blocks = (target_kb / step_kb) + 1; + memory_blocks = calloc(num_memory_blocks, sizeof(void*)); + if (!memory_blocks) { + printf("Failed to allocate memory for tracking blocks\n"); + exit(EXIT_FAILURE); + } + + int block_index = 0; + + while (allocated_kb < target_kb && !terminate) { + long long current_time = current_time_ms(); + + // Check if it's time for the next allocation + if (current_time >= next_step_time) { + // Allocate memory block + void *block = malloc(step_kb * 1024); + if (block) { + // Write to the memory to ensure it's actually allocated + memset(block, 1, step_kb * 1024); + + // Store pointer to free later + if (block_index < num_memory_blocks) { + memory_blocks[block_index++] = block; + } + + allocated_kb += step_kb; + + // Print progress + printf("[%6.2f s] Memory allocated: %zu KB (%zu MB)\n", + (current_time - start_time) / 1000.0, + allocated_kb, + allocated_kb / 1024); + + // Update next step time + next_step_time = current_time + interval_ms; + + // Print resource usage every 10MB + if (allocated_kb % (10 * 1024) == 0) { + print_resource_usage(); + } + } else { + printf("Memory allocation failed at %zu KB\n", allocated_kb); + exit(EXIT_FAILURE); + } + } + + // Small sleep to prevent CPU hogging + usleep(10000); // 10ms + } + + printf("Memory test completed: Allocated %zu KB (%zu MB)\n", allocated_kb, allocated_kb / 1024); + print_resource_usage(); +} + +// CPU test function +void test_cpu(int duration_seconds) { + printf("Starting CPU test: Duration=%d seconds\n", duration_seconds); + + long long start_time = current_time_ms(); + long long end_time = start_time + (duration_seconds * 1000); + long long next_report_time = start_time + 1000; // Report every second + + // Variables for busy work + double result = 0.0; + unsigned long iterations = 0; + + while (current_time_ms() < end_time && !terminate) { + long long current_time = current_time_ms(); + + // Do some CPU-intensive calculation + for (int i = 0; i < 1000000 && !terminate; i++) { + result += sin(result + i) * cos(result + i); + iterations++; + } + + // Check if it's time to report progress + if (current_time >= next_report_time) { + double elapsed_seconds = (current_time - start_time) / 1000.0; + double remaining_seconds = duration_seconds - elapsed_seconds; + + printf("[%6.2f s] CPU test progress: %.1f%% complete (%.1f seconds remaining)\n", + elapsed_seconds, + (elapsed_seconds / duration_seconds) * 100.0, + remaining_seconds); + + print_resource_usage(); + + next_report_time = current_time + 1000; // Next report in 1 second + } + } + + printf("CPU test completed: Performed %lu iterations\n", iterations); + print_resource_usage(); +} + +// Thread creation function +void *thread_function(void *arg) { + int thread_id = *(int*)arg; + free(arg); // Free the memory allocated for the argument + + printf("Thread %d started\n", thread_id); + + // Keep the thread alive until termination signal + while (!terminate) { + sleep(1); + } + + printf("Thread %d exiting\n", thread_id); + return NULL; +} + +// Thread test function +void test_threads(int target_threads, int interval_ms) { + printf("Starting thread test: Target=%d threads, Interval=%d ms\n", + target_threads, interval_ms); + + int created_threads = 0; + long long start_time = current_time_ms(); + long long next_step_time = start_time; + + // Allocate array for thread IDs + thread_list = calloc(target_threads, sizeof(pthread_t)); + if (!thread_list) { + printf("Failed to allocate memory for thread tracking\n"); + exit(EXIT_FAILURE); + } + + while (created_threads < target_threads && !terminate) { + long long current_time = current_time_ms(); + + // Check if it's time to create a new thread + if (current_time >= next_step_time) { + // Allocate memory for thread ID argument + int *thread_id = malloc(sizeof(int)); + if (!thread_id) { + printf("Failed to allocate memory for thread ID\n"); + break; + } + *thread_id = created_threads + 1; + + // Create a new thread + if (pthread_create(&thread_list[created_threads], NULL, thread_function, thread_id) == 0) { + created_threads++; + + // Print progress + printf("[%6.2f s] Threads created: %d\n", + (current_time - start_time) / 1000.0, + created_threads); + + // Update next step time + next_step_time = current_time + interval_ms; + + // Print resource usage every 10 threads + if (created_threads % 10 == 0) { + print_resource_usage(); + } + } else { + printf("Thread creation failed at %d threads: %s\n", created_threads, strerror(errno)); + free(thread_id); // Free the ID memory if thread creation failed + exit(EXIT_FAILURE); + } + } + + // Small sleep to prevent CPU hogging + usleep(10000); // 10ms + } + + printf("Thread test completed: Created %d threads\n", created_threads); + print_resource_usage(); + + num_threads = created_threads; +} + +void process_function() { + printf("Child process %d started\n", getpid()); + + volatile sig_atomic_t local_terminate = 0; + + void child_signal_handler(int sig) { + local_terminate = 1; + printf("Child process %d received signal %d, exiting...\n", getpid(), sig); + } + + struct sigaction sa; + sa.sa_handler = child_signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + while (!local_terminate) { + sleep(1); + } + + printf("Child process %d exiting\n", getpid()); + exit(EXIT_SUCCESS); +} + +void test_processes(int target_processes, int interval_ms) { + printf("Starting process test: Target=%d processes, Interval=%d ms\n", + target_processes, interval_ms); + + int created_processes = 0; + long long start_time = current_time_ms(); + long long next_step_time = start_time; + + process_list = calloc(target_processes, sizeof(pid_t)); + if (!process_list) { + printf("Failed to allocate memory for process tracking\n"); + exit(EXIT_FAILURE); + } + + while (created_processes < target_processes && !terminate) { + long long current_time = current_time_ms(); + + if (current_time >= next_step_time) { + pid_t pid = fork(); + + if (pid == -1) { + printf("Process creation failed at %d processes: %s\n", created_processes, strerror(errno)); + exit(EXIT_FAILURE); + } else if (pid == 0) { + process_function(); + exit(EXIT_SUCCESS); + } else { + process_list[created_processes] = pid; + created_processes++; + + printf("[%6.2f s] Processes created: %d\n", + (current_time - start_time) / 1000.0, + created_processes); + + next_step_time = current_time + interval_ms; + + if (created_processes % 10 == 0) { + print_resource_usage(); + } + } + } + + usleep(10000); // 10ms + } + + printf("Process test completed: Created %d processes\n", created_processes); + print_resource_usage(); + + num_processes = created_processes; +} + +// File size test function +void test_file_size(size_t target_mb, size_t step_kb, int interval_ms) { + printf("Starting file size test: Target=%zu MB, Step=%zu KB, Interval=%d ms\n", + target_mb, step_kb, interval_ms); + + size_t target_kb = target_mb * 1024; + size_t written_kb = 0; + long long start_time = current_time_ms(); + long long next_step_time = start_time; + + // Create buffer for writing + char *buffer = malloc(step_kb * 1024); + if (!buffer) { + printf("Failed to allocate buffer for file writing\n"); + exit(EXIT_FAILURE); + } + + // Fill buffer with random data + memset(buffer, 'A', step_kb * 1024); + + // Open file for writing + test_file = fopen(file_path, "wb"); + if (!test_file) { + printf("Failed to open file %s: %s\n", file_path, strerror(errno)); + free(buffer); + exit(EXIT_FAILURE); + } + + while (written_kb < target_kb && !terminate) { + long long current_time = current_time_ms(); + + // Check if it's time for the next write + if (current_time >= next_step_time) { + // Write to file + size_t bytes_written = fwrite(buffer, 1, step_kb * 1024, test_file); + if (bytes_written > 0) { + written_kb += bytes_written / 1024; + + // Flush to ensure data is actually written + fflush(test_file); + + // Print progress + printf("[%6.2f s] File size: %zu KB (%zu MB)\n", + (current_time - start_time) / 1000.0, + written_kb, + written_kb / 1024); + + // Update next step time + next_step_time = current_time + interval_ms; + + // Print resource usage every 10MB + if (written_kb % (10 * 1024) == 0) { + print_resource_usage(); + } + } else { + printf("File write failed at %zu KB: %s\n", written_kb, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + // Small sleep to prevent CPU hogging + usleep(10000); // 10ms + } + + free(buffer); + printf("File size test completed: Wrote %zu KB (%zu MB)\n", written_kb, written_kb / 1024); + print_resource_usage(); +} + +// Runtime test function (simple sleep) +void test_runtime(int duration_seconds) { + printf("Starting runtime test: Duration=%d seconds\n", duration_seconds); + + long long start_time = current_time_ms(); + long long end_time = start_time + (duration_seconds * 1000); + long long next_report_time = start_time + 1000; // Report every second + + while (current_time_ms() < end_time && !terminate) { + long long current_time = current_time_ms(); + + // Check if it's time to report progress + if (current_time >= next_report_time) { + double elapsed_seconds = (current_time - start_time) / 1000.0; + double remaining_seconds = duration_seconds - elapsed_seconds; + + printf("[%6.2f s] Runtime test progress: %.1f%% complete (%.1f seconds remaining)\n", + elapsed_seconds, + (elapsed_seconds / duration_seconds) * 100.0, + remaining_seconds); + + print_resource_usage(); + + next_report_time = current_time + 1000; // Next report in 1 second + } + + // Sleep for a short time + usleep(100000); // 100ms + } + + printf("Runtime test completed: Slept for %.2f seconds\n", (current_time_ms() - start_time) / 1000.0); + print_resource_usage(); +} + +// Cleanup function +void cleanup() { + printf("Cleaning up resources...\n"); + + // Free memory blocks + if (memory_blocks) { + for (int i = 0; i < num_memory_blocks && memory_blocks[i]; i++) { + free(memory_blocks[i]); + } + free(memory_blocks); + memory_blocks = NULL; + } + + // Wait for threads to terminate + if (thread_list) { + for (int i = 0; i < num_threads; i++) { + pthread_join(thread_list[i], NULL); + } + free(thread_list); + thread_list = NULL; + } + + // Terminate and wait for child processes + if (process_list) { + printf("Terminating %d child processes...\n", num_processes); + for (int i = 0; i < num_processes; i++) { + if (process_list[i] > 0) { + kill(process_list[i], SIGTERM); + } + } + + printf("Waiting for child processes to exit...\n"); + + int timeout_seconds = 5; + time_t start_wait = time(NULL); + + int remaining = num_processes; + while (remaining > 0 && time(NULL) - start_wait < timeout_seconds) { + for (int i = 0; i < num_processes; i++) { + if (process_list[i] > 0) { + int status; + pid_t result = waitpid(process_list[i], &status, WNOHANG); + + if (result > 0) { + if (WIFEXITED(status)) { + printf("Process %d exited with status %d\n", + (int)process_list[i], WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("Process %d killed by signal %d\n", + (int)process_list[i], WTERMSIG(status)); + } + process_list[i] = -1; + remaining--; + } + } + } + usleep(100000); + } + + if (remaining > 0) { + printf("Force killing %d remaining processes...\n", remaining); + for (int i = 0; i < num_processes; i++) { + if (process_list[i] > 0) { + kill(process_list[i], SIGKILL); + waitpid(process_list[i], NULL, 0); + printf("Process %d force killed\n", (int)process_list[i]); + } + } + } + + free(process_list); + process_list = NULL; + } + + // Close and remove test file + if (test_file) { + fclose(test_file); + test_file = NULL; + if (file_path) { + unlink(file_path); + } + } + + // Free file path + if (file_path && strcasecmp(file_path, DEFAULT_FILE_PATH) != 0) { + free(file_path); + file_path = NULL; + } + + printf("Cleanup completed\n"); +} + +// Print usage +void print_usage(const char *program_name) { + printf("Usage: %s [options]\n", program_name); + printf(" Required: Duration to run in seconds\n"); + printf("Options:\n"); + printf(" -m, --memory MB Target memory usage in MB\n"); + printf(" -c, --cpu Enable CPU stress test\n"); + printf(" -t, --threads N Number of threads to create\n"); + printf(" -p, --processes N Number of processes to create\n"); + printf(" -f, --filesize MB Target file size in MB\n"); + printf(" -P, --filepath PATH Path for file size test (default: %s)\n", DEFAULT_FILE_PATH); + printf(" -s, --step KB Step size in KB (default: %d)\n", DEFAULT_STEP_SIZE_KB); + printf(" -i, --interval MS Interval between steps in ms (default: %d)\n", DEFAULT_STEP_INTERVAL_MS); + printf(" -h, --help Show this help message\n"); + printf("\nExample:\n"); + printf(" %s 60 -m 500 -t 50 -p 10 -s 1024 -i 100\n", program_name); + printf(" (Run for 60 seconds, allocate up to 500MB memory, create 50 threads,\n"); + printf(" create 10 processes, with step size of 1MB every 100ms)\n"); +} + +int main(int argc, char *argv[]) { + // Default parameters + int sleep_time = 0; + size_t memory_mb = 0; + int cpu_test = 0; + int thread_count = 0; + int process_count = 0; + size_t file_size_mb = 0; + size_t step_size_kb = DEFAULT_STEP_SIZE_KB; + int step_interval_ms = DEFAULT_STEP_INTERVAL_MS; + file_path = (char*)DEFAULT_FILE_PATH; + + // Define long options + static struct option long_options[] = { + {"memory", required_argument, 0, 'm'}, + {"cpu", no_argument, 0, 'c'}, + {"threads", required_argument, 0, 't'}, + {"processes", required_argument, 0, 'p'}, + {"filesize", required_argument, 0, 'f'}, + {"filepath", required_argument, 0, 'P'}, + {"step", required_argument, 0, 's'}, + {"interval", required_argument, 0, 'i'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + // Check if sleep_time is provided + if (argc < 2) { + print_usage(argv[0]); + return EXIT_FAILURE; + } + + // Parse the sleep_time argument + sleep_time = atoi(argv[1]); + if (sleep_time <= 0) { + printf("Error: Sleep time must be a positive integer\n"); + print_usage(argv[0]); + return EXIT_FAILURE; + } + + // Shift the arguments to make getopt work + for (int i = 1; i < argc - 1; i++) { + argv[i] = argv[i + 1]; + } + argc--; + + // Parse options + int option_index = 0; + int opt; + while ((opt = getopt_long(argc, argv, "m:ct:p:f:P:s:i:h", long_options, &option_index)) != -1) { + switch (opt) { + case 'm': + memory_mb = atoi(optarg); + break; + case 'c': + cpu_test = 1; + break; + case 't': + thread_count = atoi(optarg); + break; + case 'p': + process_count = atoi(optarg); + break; + case 'f': + file_size_mb = atoi(optarg); + break; + case 'P': + file_path = strdup(optarg); + break; + case 's': + step_size_kb = atoi(optarg); + break; + case 'i': + step_interval_ms = atoi(optarg); + break; + case 'h': + print_usage(argv[0]); + return EXIT_SUCCESS; + case '?': + // getopt_long already printed an error message + print_usage(argv[0]); + return EXIT_FAILURE; + default: + break; + } + } + + // Validate parameters + if (step_size_kb <= 0) { + printf("Warning: Invalid step size, using default %d KB\n", DEFAULT_STEP_SIZE_KB); + step_size_kb = DEFAULT_STEP_SIZE_KB; + } + + if (step_interval_ms <= 0) { + printf("Warning: Invalid interval, using default %d ms\n", DEFAULT_STEP_INTERVAL_MS); + step_interval_ms = DEFAULT_STEP_INTERVAL_MS; + } + + // Set up signal handlers + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + // Set up cleanup handler + atexit(cleanup); + + // Print test configuration + printf("Resource Limit Test Configuration:\n"); + printf("- Sleep time: %d seconds\n", sleep_time); + printf("- Memory test: %s", memory_mb > 0 ? "Enabled" : "Disabled"); + if (memory_mb > 0) printf(" (Target: %zu MB)", memory_mb); + printf("\n"); + + printf("- CPU test: %s\n", cpu_test ? "Enabled" : "Disabled"); + + printf("- Thread test: %s", thread_count > 0 ? "Enabled" : "Disabled"); + if (thread_count > 0) printf(" (Target: %d threads)", thread_count); + printf("\n"); + + printf("- Process test: %s", process_count > 0 ? "Enabled" : "Disabled"); + if (process_count > 0) printf(" (Target: %d processes)", process_count); + printf("\n"); + + printf("- File size test: %s", file_size_mb > 0 ? "Enabled" : "Disabled"); + if (file_size_mb > 0) printf(" (Target: %zu MB, Path: %s)", file_size_mb, file_path); + printf("\n"); + + printf("- Step size: %zu KB\n", step_size_kb); + printf("- Step interval: %d ms\n", step_interval_ms); + printf("\n"); + + // Record start time + long long start_time = current_time_ms(); + + // Run tests based on configuration + if (memory_mb > 0) { + test_memory(memory_mb, step_size_kb, step_interval_ms); + } + + if (thread_count > 0) { + test_threads(thread_count, step_interval_ms); + } + + if (process_count > 0) { + test_processes(process_count, step_interval_ms); + } + + if (file_size_mb > 0) { + test_file_size(file_size_mb, step_size_kb, step_interval_ms); + } + + if (cpu_test) { + test_cpu(sleep_time); + } else { + // If no CPU test, just sleep for the specified duration + test_runtime(sleep_time); + } + + terminate = 1; + + if (thread_count > 0) { + printf("Waiting for threads to exit...\n"); + usleep(500000); + } + + // Print overall runtime + printf("\nTest completed in %.2f seconds\n", (current_time_ms() - start_time) / 1000.0); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/worker/test/test_ipc.c b/src/worker/test/test_ipc.c new file mode 100644 index 00000000..b7c5baaf --- /dev/null +++ b/src/worker/test/test_ipc.c @@ -0,0 +1,371 @@ +#include "uv_ipc.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define SOCKET_PATH "/tmp/ipc_test.sock" +#define CHILD_PROCESS_COUNT 5 + +// 进程类型 +typedef enum { + PROCESS_MASTER, + PROCESS_WORKER +} process_type_t; + +static process_type_t process_type; + +// 全局变量,需要从uv_ipc.c中访问 +int child_processes = 0; +int active_jobs = 0; +int exit_flag = 0; + +void run_worker(); + +// 处理闹钟超时 +void alarm_handler(int sig) { + if (sig == SIGALRM) { + printf("\n[主进程] 警告:程序运行超时,强制退出\n"); + + // 强制杀死所有子进程 + kill(0, SIGKILL); + + // 打印当前状态 + printf("[主进程] 强制退出时状态:child_processes=%d, active_jobs=%d, exit_flag=%d\n", + child_processes, active_jobs, exit_flag); + + // 直接退出,不进行任何清理 + _exit(1); + } +} + +// 主进程: 处理子进程任务状态回调 +void master_job_status_callback(uint64_t job_id, int status_code, const char* status_message) { + if (!status_message) { + printf("[主进程] 警告: 收到空消息,job_id=%lu, status_code=%d\n", + job_id, status_code); + return; + } + + printf("[主进程] 收到任务状态: Job ID %lu, 状态码 %d, 消息: %s\n", + job_id, status_code, status_message); + + // 任务完成的情况有两种: + // 1. MSG_JOB_COMPLETED 消息(status_code=0, message="Completed") + // 2. 最后一个状态消息(status_code=1, message="Completed") + if ((status_code == 0 && strncmp(status_message, "Completed", 9) == 0) || + (status_code == 1 && strncmp(status_message, "Completed", 9) == 0)) { + + // 使用静态变量跟踪已完成的任务,避免重复计数 + static uint64_t completed_jobs[1024] = {0}; + static int completed_count = 0; + + // 检查是否已记录过这个任务 + bool already_completed = false; + for (int i = 0; i < completed_count; i++) { + if (completed_jobs[i] == job_id) { + already_completed = true; + break; + } + } + + if (!already_completed && completed_count < 1024) { + completed_jobs[completed_count++] = job_id; + active_jobs--; + printf("[主进程] 任务 %lu 已完成, 剩余活跃任务: %d\n", job_id, active_jobs); + } + } +} + +// 主进程: 处理子进程断开连接回调 +void master_disconnect_callback(uint64_t client_id) { + printf("[主进程] 子进程 %lu 已断开连接\n", client_id); + child_processes--; + + printf("[主进程] 剩余子进程: %d, 剩余活跃任务: %d\n", child_processes, active_jobs); + + // 如果所有子进程都已断开连接,可以考虑设置退出标志 + if (child_processes <= 0 && active_jobs <= 0) { + printf("[主进程] 所有子进程已断开连接且没有活跃任务,设置退出标志\n"); + exit_flag = 1; + } +} + +// 子进程: 连接回调 +void worker_connection_callback(bool success) { + if (success) { + printf("[子进程 %d] 已连接到主进程\n", getpid()); + // 连接成功后请求任务 + ipc_worker_request_job(); + } else { + printf("[子进程 %d] 连接到主进程失败\n", getpid()); + _exit(1); + } +} + +// 子进程: 任务回调 +void worker_job_callback(uint64_t job_id, const char* cmd) { + printf("[子进程 %d] 收到任务: Job ID %lu, 命令: %s\n", getpid(), job_id, cmd); + + // 模拟任务进行 + printf("[子进程 %d] 开始执行任务 %lu\n", getpid(), job_id); + + // 发送状态更新 + ipc_worker_send_status(job_id, 1, "Running"); + + // 模拟随机执行时间(较短以便于测试) + //int execution_time = rand() % 3 + 1; + int execution_time = 3; + printf("[子进程 %d] 任务 %lu 将运行 %d 秒\n", getpid(), job_id, execution_time); + + // 模拟任务进度报告 + for (int i = 1; i <= execution_time; i++) { + usleep(10 * 1000); // 10ms + char status[256]; + int progress = (i * 100) / execution_time; + + // 清除status缓冲区,避免垃圾数据 + memset(status, 0, sizeof(status)); + + if (progress < 100) { + snprintf(status, sizeof(status), "Progress %d%%", progress); + ipc_worker_send_status(job_id, 1, status); + } + } + + // 发送任务完成消息 - 使用 MSG_JOB_COMPLETED 类型 + printf("[子进程 %d] 任务 %lu 已完成\n", getpid(), job_id); + + // 先发送一个最终状态,确保主进程知道我们完成了 + ipc_worker_send_status(job_id, 1, "Completed"); + + // 等待一小段时间确保上面的消息已发送 + usleep(50000); // 50ms + + // 发送正式的完成消息 + ipc_worker_job_completed(job_id); + + // 等待另一小段时间确保完成消息被发送 + usleep(100000); // 100ms + + // 关闭连接,模拟子进程完成后退出 + printf("[子进程 %d] 准备断开连接并退出\n", getpid()); + + // 停止libuv相关资源(不会尝试关闭或删除事件循环) + ipc_worker_stop(); + + printf("[子进程 %d] 即将退出\n", getpid()); + fflush(stdout); // 确保所有输出都被写出 + + // 最后一次等待,确保所有消息都已发送 + usleep(200000); // 200ms + + _exit(0); // 立即退出,不执行任何清理 +} + +void signal_handler(int sig) { + if (sig == SIGINT || sig == SIGTERM) { + printf("\n收到信号 %d,准备退出\n", sig); + fflush(stdout); + + if (process_type == PROCESS_MASTER) { + exit_flag = 1; + + // 向所有子进程发送中断信号 + printf("[主进程] 向子进程发送终止信号\n"); + kill(0, SIGTERM); // 使用SIGTERM而不是SIGINT + + // 如果没有子进程,立即停止事件循环 + if (child_processes <= 0) { + printf("[主进程] 没有子进程,直接退出\n"); + if (loop) uv_stop(loop); + return; + } + } else { + // 子进程直接退出,不做任何清理 + printf("[子进程 %d] 收到信号,直接退出\n", getpid()); + fflush(stdout); + _exit(0); + } + } +} + +void run_master() { + process_type = PROCESS_MASTER; + + printf("[主进程] 启动中...\n"); + + // 设置随机种子 + srand((unsigned int)time(NULL)); + + // 初始化主进程 + if (!ipc_master_init(SOCKET_PATH)) { + fprintf(stderr, "[主进程] 初始化失败\n"); + exit(1); + } + + // 设置回调 + ipc_master_set_status_callback(master_job_status_callback); + ipc_master_set_disconnect_callback(master_disconnect_callback); + + printf("[主进程] 初始化完成,等待子进程连接...\n"); + + // 启动子进程 + for (int i = 0; i < CHILD_PROCESS_COUNT; i++) { + pid_t pid = fork(); + + if (pid < 0) { + fprintf(stderr, "[主进程] 创建子进程失败\n"); + exit(1); + } else if (pid == 0) { + // 子进程 - 确保不继承父进程的状态 + child_processes = 0; // 重置子进程中的计数器 + active_jobs = 0; + exit_flag = 0; + + run_worker(); + _exit(0); // 使用_exit确保不执行父进程的atexit处理程序 + } else { + // 主进程 - 记录子进程 + printf("[主进程] 子进程 %d 已创建\n", pid); + child_processes++; + active_jobs++; + } + } + + // 运行主进程事件循环 + ipc_master_run(); + + printf("[主进程] 事件循环已退出,等待子进程结束...\n"); + + // 确保所有子进程退出 + int status; + pid_t wpid; + + // 先等待一小段时间,让子进程有机会自然退出 + usleep(100000); // 等待100ms + + // 等待所有子进程退出 + int remaining = 0; + while ((wpid = waitpid(-1, &status, WNOHANG)) > 0) { + printf("[主进程] 子进程 %d 已退出,状态码: %d\n", + wpid, WIFEXITED(status) ? WEXITSTATUS(status) : -1); + } + + // 检查是否还有子进程 + pid_t* child_pids = malloc(sizeof(pid_t) * 100); + if (child_pids) { + int remaining = 0; + FILE* fp = popen("ps -o pid -p $$ | tail -n +2", "r"); // 使用ps更可靠 + if (fp) { + char line[32]; + while (fgets(line, sizeof(line), fp)) { + pid_t child_pid = atoi(line); + if (child_pid > 0) { + printf("[主进程] 发现仍在运行的子进程: %d,发送终止信号\n", child_pid); + kill(child_pid, SIGTERM); + remaining++; + } + } + pclose(fp); + } + free(child_pids); + + if (remaining > 0) { + printf("[主进程] 等待 %d 个子进程终止...\n", remaining); + sleep(1); // 给子进程一些时间来处理SIGTERM + + // 使用ps再次检查并用SIGKILL终止顽固的进程 + fp = popen("ps -o pid -p $$ | tail -n +2", "r"); + if (fp) { + char line[32]; + while (fgets(line, sizeof(line), fp)) { + pid_t child_pid = atoi(line); + if (child_pid > 0) { + printf("[主进程] 子进程 %d 未响应 SIGTERM, 发送 SIGKILL\n", child_pid); + kill(child_pid, SIGKILL); + } + } + pclose(fp); + } + } + } + + printf("[主进程] 所有子进程已退出,测试完成\n"); +} + +void run_worker() { + process_type = PROCESS_WORKER; + + // 设置随机种子,使不同子进程行为不同 + srand(getpid() + (unsigned int)time(NULL)); + + printf("[子进程 %d] 启动中...\n", getpid()); + + // 初始化子进程 + if (!ipc_worker_init(SOCKET_PATH)) { + fprintf(stderr, "[子进程 %d] 初始化失败\n", getpid()); + _exit(1); + } + + // 设置回调 + ipc_worker_set_job_callback(worker_job_callback); + + // 连接到主进程 + ipc_worker_connect(worker_connection_callback); + + // 运行子进程事件循环 + ipc_worker_run(); + + printf("[子进程 %d] 事件循环已退出\n", getpid()); + + // 确保子进程立即退出,不执行任何清理操作 + fflush(stdout); + _exit(0); +} + +int main() { + // 初始化全局变量 + child_processes = 0; + active_jobs = 0; + exit_flag = 0; + + // 忽略SIGPIPE信号,它可能在写入已关闭的pipe时发生 + signal(SIGPIPE, SIG_IGN); + + // 设置信号处理 + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sa.sa_flags = SA_RESTART; // 使被中断的系统调用自动重启 + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + // 设置闹钟信号处理 + struct sigaction sa_alrm; + memset(&sa_alrm, 0, sizeof(sa_alrm)); + sa_alrm.sa_handler = alarm_handler; + sigaction(SIGALRM, &sa_alrm, NULL); + + // 设置超时机制,防止程序永久挂起 + alarm(30); // 如果30秒后程序仍未退出,将发送SIGALRM + + // 处理子进程退出,避免僵尸进程 + struct sigaction sa_chld; + memset(&sa_chld, 0, sizeof(sa_chld)); + sa_chld.sa_handler = SIG_DFL; + sa_chld.sa_flags = SA_NOCLDWAIT; // 不创建僵尸进程 + sigaction(SIGCHLD, &sa_chld, NULL); + + // 作为主进程运行 + run_master(); + + // 确保清理任何残留资源 + printf("[主进程] 程序退出\n"); + + return 0; +} diff --git a/src/worker/test/unity.c b/src/worker/test/unity.c new file mode 100644 index 00000000..7fa2dc63 --- /dev/null +++ b/src/worker/test/unity.c @@ -0,0 +1,2501 @@ +/* ========================================================================= + Unity - A Test Framework for C + ThrowTheSwitch.org + Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "unity.h" + +#ifndef UNITY_PROGMEM +#define UNITY_PROGMEM +#endif + +/* If omitted from header, declare overrideable prototypes here so they're ready for use */ +#ifdef UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION +void UNITY_OUTPUT_CHAR(int); +#endif + +/* Helpful macros for us to use here in Assert functions */ +#define UNITY_FAIL_AND_BAIL do { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) +#define UNITY_IGNORE_AND_BAIL do { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) +#define RETURN_IF_FAIL_OR_IGNORE do { if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) { TEST_ABORT(); } } while (0) + +struct UNITY_STORAGE_T Unity; + +#ifdef UNITY_OUTPUT_COLOR +const char UNITY_PROGMEM UnityStrOk[] = "\033[42mOK\033[0m"; +const char UNITY_PROGMEM UnityStrPass[] = "\033[42mPASS\033[0m"; +const char UNITY_PROGMEM UnityStrFail[] = "\033[41mFAIL\033[0m"; +const char UNITY_PROGMEM UnityStrIgnore[] = "\033[43mIGNORE\033[0m"; +#else +const char UNITY_PROGMEM UnityStrOk[] = "OK"; +const char UNITY_PROGMEM UnityStrPass[] = "PASS"; +const char UNITY_PROGMEM UnityStrFail[] = "FAIL"; +const char UNITY_PROGMEM UnityStrIgnore[] = "IGNORE"; +#endif +static const char UNITY_PROGMEM UnityStrNull[] = "NULL"; +static const char UNITY_PROGMEM UnityStrSpacer[] = ". "; +static const char UNITY_PROGMEM UnityStrExpected[] = " Expected "; +static const char UNITY_PROGMEM UnityStrWas[] = " Was "; +static const char UNITY_PROGMEM UnityStrGt[] = " to be greater than "; +static const char UNITY_PROGMEM UnityStrLt[] = " to be less than "; +static const char UNITY_PROGMEM UnityStrOrEqual[] = "or equal to "; +static const char UNITY_PROGMEM UnityStrNotEqual[] = " to be not equal to "; +static const char UNITY_PROGMEM UnityStrElement[] = " Element "; +static const char UNITY_PROGMEM UnityStrByte[] = " Byte "; +static const char UNITY_PROGMEM UnityStrMemory[] = " Memory Mismatch."; +static const char UNITY_PROGMEM UnityStrDelta[] = " Values Not Within Delta "; +static const char UNITY_PROGMEM UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; +static const char UNITY_PROGMEM UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; +static const char UNITY_PROGMEM UnityStrNullPointerForActual[] = " Actual pointer was NULL"; +#ifndef UNITY_EXCLUDE_FLOAT +static const char UNITY_PROGMEM UnityStrNot[] = "Not "; +static const char UNITY_PROGMEM UnityStrInf[] = "Infinity"; +static const char UNITY_PROGMEM UnityStrNegInf[] = "Negative Infinity"; +static const char UNITY_PROGMEM UnityStrNaN[] = "NaN"; +static const char UNITY_PROGMEM UnityStrDet[] = "Determinate"; +static const char UNITY_PROGMEM UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; +#endif +const char UNITY_PROGMEM UnityStrErrShorthand[] = "Unity Shorthand Support Disabled"; +const char UNITY_PROGMEM UnityStrErrFloat[] = "Unity Floating Point Disabled"; +const char UNITY_PROGMEM UnityStrErrDouble[] = "Unity Double Precision Disabled"; +const char UNITY_PROGMEM UnityStrErr64[] = "Unity 64-bit Support Disabled"; +static const char UNITY_PROGMEM UnityStrBreaker[] = "-----------------------"; +static const char UNITY_PROGMEM UnityStrResultsTests[] = " Tests "; +static const char UNITY_PROGMEM UnityStrResultsFailures[] = " Failures "; +static const char UNITY_PROGMEM UnityStrResultsIgnored[] = " Ignored "; +#ifndef UNITY_EXCLUDE_DETAILS +static const char UNITY_PROGMEM UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; +static const char UNITY_PROGMEM UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; +#endif +/*----------------------------------------------- + * Pretty Printers & Test Result Output Handlers + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +/* Local helper function to print characters. */ +static void UnityPrintChar(const char* pch) +{ + /* printable characters plus CR & LF are printed */ + if ((*pch <= 126) && (*pch >= 32)) + { + UNITY_OUTPUT_CHAR(*pch); + } + /* write escaped carriage returns */ + else if (*pch == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (*pch == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)*pch, 2); + } +} + +/*-----------------------------------------------*/ +/* Local helper function to print ANSI escape strings e.g. "\033[42m". */ +#ifdef UNITY_OUTPUT_COLOR +static UNITY_UINT UnityPrintAnsiEscapeString(const char* string) +{ + const char* pch = string; + UNITY_UINT count = 0; + + while (*pch && (*pch != 'm')) + { + UNITY_OUTPUT_CHAR(*pch); + pch++; + count++; + } + UNITY_OUTPUT_CHAR('m'); + count++; + + return count; +} +#endif + +/*-----------------------------------------------*/ +void UnityPrint(const char* string) +{ + const char* pch = string; + + if (pch != NULL) + { + while (*pch) + { +#ifdef UNITY_OUTPUT_COLOR + /* print ANSI escape code */ + if ((*pch == 27) && (*(pch + 1) == '[')) + { + pch += UnityPrintAnsiEscapeString(pch); + continue; + } +#endif + UnityPrintChar(pch); + pch++; + } + } +} +/*-----------------------------------------------*/ +void UnityPrintLen(const char* string, const UNITY_UINT32 length) +{ + const char* pch = string; + + if (pch != NULL) + { + while (*pch && ((UNITY_UINT32)(pch - string) < length)) + { + /* printable characters plus CR & LF are printed */ + if ((*pch <= 126) && (*pch >= 32)) + { + UNITY_OUTPUT_CHAR(*pch); + } + /* write escaped carriage returns */ + else if (*pch == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (*pch == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)*pch, 2); + } + pch++; + } + } +} + +/*-----------------------------------------------*/ +void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style) +{ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (style == UNITY_DISPLAY_STYLE_CHAR) + { + /* printable characters plus CR & LF are printed */ + UNITY_OUTPUT_CHAR('\''); + if ((number <= 126) && (number >= 32)) + { + UNITY_OUTPUT_CHAR((int)number); + } + /* write escaped carriage returns */ + else if (number == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (number == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)number, 2); + } + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrintNumber(number); + } + } + else if ((style & UNITY_DISPLAY_RANGE_UINT) == UNITY_DISPLAY_RANGE_UINT) + { + UnityPrintNumberUnsigned((UNITY_UINT)number); + } + else + { + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)number, (char)((style & 0xF) * 2)); + } +} + +/*-----------------------------------------------*/ +void UnityPrintNumber(const UNITY_INT number_to_print) +{ + UNITY_UINT number = (UNITY_UINT)number_to_print; + + if (number_to_print < 0) + { + /* A negative number, including MIN negative */ + UNITY_OUTPUT_CHAR('-'); + number = (~number) + 1; + } + UnityPrintNumberUnsigned(number); +} + +/*----------------------------------------------- + * basically do an itoa using as little ram as possible */ +void UnityPrintNumberUnsigned(const UNITY_UINT number) +{ + UNITY_UINT divisor = 1; + + /* figure out initial divisor */ + while (number / divisor > 9) + { + divisor *= 10; + } + + /* now mod and print, then divide divisor */ + do + { + UNITY_OUTPUT_CHAR((char)('0' + (number / divisor % 10))); + divisor /= 10; + } while (divisor > 0); +} + +/*-----------------------------------------------*/ +void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print) +{ + int nibble; + char nibbles = nibbles_to_print; + + if ((unsigned)nibbles > UNITY_MAX_NIBBLES) + { + nibbles = UNITY_MAX_NIBBLES; + } + + while (nibbles > 0) + { + nibbles--; + nibble = (int)(number >> (nibbles * 4)) & 0x0F; + if (nibble <= 9) + { + UNITY_OUTPUT_CHAR((char)('0' + nibble)); + } + else + { + UNITY_OUTPUT_CHAR((char)('A' - 10 + nibble)); + } + } +} + +/*-----------------------------------------------*/ +void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number) +{ + UNITY_UINT current_bit = (UNITY_UINT)1 << (UNITY_INT_WIDTH - 1); + UNITY_INT32 i; + + for (i = 0; i < UNITY_INT_WIDTH; i++) + { + if (current_bit & mask) + { + if (current_bit & number) + { + UNITY_OUTPUT_CHAR('1'); + } + else + { + UNITY_OUTPUT_CHAR('0'); + } + } + else + { + UNITY_OUTPUT_CHAR('X'); + } + current_bit = current_bit >> 1; + } +} + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +/* + * This function prints a floating-point value in a format similar to + * printf("%.7g") on a single-precision machine or printf("%.9g") on a + * double-precision machine. The 7th digit won't always be totally correct + * in single-precision operation (for that level of accuracy, a more + * complicated algorithm would be needed). + */ +void UnityPrintFloat(const UNITY_DOUBLE input_number) +{ +#ifdef UNITY_INCLUDE_DOUBLE + static const int sig_digits = 9; + static const UNITY_INT32 min_scaled = 100000000; + static const UNITY_INT32 max_scaled = 1000000000; +#else + static const int sig_digits = 7; + static const UNITY_INT32 min_scaled = 1000000; + static const UNITY_INT32 max_scaled = 10000000; +#endif + + UNITY_DOUBLE number = input_number; + + /* print minus sign (does not handle negative zero) */ + if (number < 0.0f) + { + UNITY_OUTPUT_CHAR('-'); + number = -number; + } + + /* handle zero, NaN, and +/- infinity */ + if (number == 0.0f) + { + UnityPrint("0"); + } + else if (UNITY_IS_NAN(number)) + { + UnityPrint("nan"); + } + else if (UNITY_IS_INF(number)) + { + UnityPrint("inf"); + } + else + { + UNITY_INT32 n_int = 0; + UNITY_INT32 n; + int exponent = 0; + int decimals; + int digits; + char buf[16] = {0}; + + /* + * Scale up or down by powers of 10. To minimize rounding error, + * start with a factor/divisor of 10^10, which is the largest + * power of 10 that can be represented exactly. Finally, compute + * (exactly) the remaining power of 10 and perform one more + * multiplication or division. + */ + if (number < 1.0f) + { + UNITY_DOUBLE factor = 1.0f; + + while (number < (UNITY_DOUBLE)max_scaled / 1e10f) { number *= 1e10f; exponent -= 10; } + while (number * factor < (UNITY_DOUBLE)min_scaled) { factor *= 10.0f; exponent--; } + + number *= factor; + } + else if (number > (UNITY_DOUBLE)max_scaled) + { + UNITY_DOUBLE divisor = 1.0f; + + while (number > (UNITY_DOUBLE)min_scaled * 1e10f) { number /= 1e10f; exponent += 10; } + while (number / divisor > (UNITY_DOUBLE)max_scaled) { divisor *= 10.0f; exponent++; } + + number /= divisor; + } + else + { + /* + * In this range, we can split off the integer part before + * doing any multiplications. This reduces rounding error by + * freeing up significant bits in the fractional part. + */ + UNITY_DOUBLE factor = 1.0f; + n_int = (UNITY_INT32)number; + number -= (UNITY_DOUBLE)n_int; + + while (n_int < min_scaled) { n_int *= 10; factor *= 10.0f; exponent--; } + + number *= factor; + } + + /* round to nearest integer */ + n = ((UNITY_INT32)(number + number) + 1) / 2; + +#ifndef UNITY_ROUND_TIES_AWAY_FROM_ZERO + /* round to even if exactly between two integers */ + if ((n & 1) && (((UNITY_DOUBLE)n - number) == 0.5f)) + n--; +#endif + + n += n_int; + + if (n >= max_scaled) + { + n = min_scaled; + exponent++; + } + + /* determine where to place decimal point */ + decimals = ((exponent <= 0) && (exponent >= -(sig_digits + 3))) ? (-exponent) : (sig_digits - 1); + exponent += decimals; + + /* truncate trailing zeroes after decimal point */ + while ((decimals > 0) && ((n % 10) == 0)) + { + n /= 10; + decimals--; + } + + /* build up buffer in reverse order */ + digits = 0; + while ((n != 0) || (digits <= decimals)) + { + buf[digits++] = (char)('0' + n % 10); + n /= 10; + } + + /* print out buffer (backwards) */ + while (digits > 0) + { + if (digits == decimals) + { + UNITY_OUTPUT_CHAR('.'); + } + UNITY_OUTPUT_CHAR(buf[--digits]); + } + + /* print exponent if needed */ + if (exponent != 0) + { + UNITY_OUTPUT_CHAR('e'); + + if (exponent < 0) + { + UNITY_OUTPUT_CHAR('-'); + exponent = -exponent; + } + else + { + UNITY_OUTPUT_CHAR('+'); + } + + digits = 0; + while ((exponent != 0) || (digits < 2)) + { + buf[digits++] = (char)('0' + exponent % 10); + exponent /= 10; + } + while (digits > 0) + { + UNITY_OUTPUT_CHAR(buf[--digits]); + } + } + } +} +#endif /* ! UNITY_EXCLUDE_FLOAT_PRINT */ + +/*-----------------------------------------------*/ +static void UnityTestResultsBegin(const char* file, const UNITY_LINE_TYPE line) +{ +#ifdef UNITY_OUTPUT_FOR_ECLIPSE + UNITY_OUTPUT_CHAR('('); + UnityPrint(file); + UNITY_OUTPUT_CHAR(':'); + UnityPrintNumber((UNITY_INT)line); + UNITY_OUTPUT_CHAR(')'); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(Unity.CurrentTestName); + UNITY_OUTPUT_CHAR(':'); +#else +#ifdef UNITY_OUTPUT_FOR_IAR_WORKBENCH + UnityPrint("'); + UnityPrint(Unity.CurrentTestName); + UnityPrint(" "); +#else +#ifdef UNITY_OUTPUT_FOR_QT_CREATOR + UnityPrint("file://"); + UnityPrint(file); + UNITY_OUTPUT_CHAR(':'); + UnityPrintNumber((UNITY_INT)line); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(Unity.CurrentTestName); + UNITY_OUTPUT_CHAR(':'); +#else + UnityPrint(file); + UNITY_OUTPUT_CHAR(':'); + UnityPrintNumber((UNITY_INT)line); + UNITY_OUTPUT_CHAR(':'); + UnityPrint(Unity.CurrentTestName); + UNITY_OUTPUT_CHAR(':'); +#endif +#endif +#endif +} + +/*-----------------------------------------------*/ +static void UnityTestResultsFailBegin(const UNITY_LINE_TYPE line) +{ + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrFail); + UNITY_OUTPUT_CHAR(':'); +} + +/*-----------------------------------------------*/ +void UnityConcludeTest(void) +{ + if (Unity.CurrentTestIgnored) + { + Unity.TestIgnores++; + } + else if (!Unity.CurrentTestFailed) + { + UnityTestResultsBegin(Unity.TestFile, Unity.CurrentTestLineNumber); + UnityPrint(UnityStrPass); + } + else + { + Unity.TestFailures++; + } + + Unity.CurrentTestFailed = 0; + Unity.CurrentTestIgnored = 0; + UNITY_PRINT_EXEC_TIME(); + UNITY_PRINT_EOL(); + UNITY_FLUSH_CALL(); +} + +/*-----------------------------------------------*/ +static void UnityAddMsgIfSpecified(const char* msg) +{ +#ifdef UNITY_PRINT_TEST_CONTEXT + UnityPrint(UnityStrSpacer); + UNITY_PRINT_TEST_CONTEXT(); +#endif +#ifndef UNITY_EXCLUDE_DETAILS + if (Unity.CurrentDetail1) + { + UnityPrint(UnityStrSpacer); + UnityPrint(UnityStrDetail1Name); + UnityPrint(Unity.CurrentDetail1); + if (Unity.CurrentDetail2) + { + UnityPrint(UnityStrDetail2Name); + UnityPrint(Unity.CurrentDetail2); + } + } +#endif + if (msg) + { + UnityPrint(UnityStrSpacer); + UnityPrint(msg); + } +} + +/*-----------------------------------------------*/ +static void UnityPrintExpectedAndActualStrings(const char* expected, const char* actual) +{ + UnityPrint(UnityStrExpected); + if (expected != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrint(expected); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } + UnityPrint(UnityStrWas); + if (actual != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrint(actual); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } +} + +/*-----------------------------------------------*/ +static void UnityPrintExpectedAndActualStringsLen(const char* expected, + const char* actual, + const UNITY_UINT32 length) +{ + UnityPrint(UnityStrExpected); + if (expected != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrintLen(expected, length); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } + UnityPrint(UnityStrWas); + if (actual != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrintLen(actual, length); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } +} + +/*----------------------------------------------- + * Assertion & Control Helpers + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +static int UnityIsOneArrayNull(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_LINE_TYPE lineNumber, + const char* msg) +{ + /* Both are NULL or same pointer */ + if (expected == actual) { return 0; } + + /* print and return true if just expected is NULL */ + if (expected == NULL) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrNullPointerForExpected); + UnityAddMsgIfSpecified(msg); + return 1; + } + + /* print and return true if just actual is NULL */ + if (actual == NULL) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrNullPointerForActual); + UnityAddMsgIfSpecified(msg); + return 1; + } + + return 0; /* return false if neither is NULL */ +} + +/*----------------------------------------------- + * Assertion Functions + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +void UnityAssertBits(const UNITY_INT mask, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if ((mask & expected) != (mask & actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)expected); + UnityPrint(UnityStrWas); + UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualNumber(const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (expected != actual) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expected, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, + const UNITY_INT actual, + const UNITY_COMPARISON_T compare, + const char *msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + int failed = 0; + RETURN_IF_FAIL_OR_IGNORE; + + if ((threshold == actual) && (compare & UNITY_EQUAL_TO)) { return; } + if ((threshold == actual)) { failed = 1; } + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if ((actual > threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if ((actual < threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + } + else /* UINT or HEX */ + { + if (((UNITY_UINT)actual > (UNITY_UINT)threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if (((UNITY_UINT)actual < (UNITY_UINT)threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + } + + if (failed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(actual, style); + if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } + if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } + if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } + if (compare == UNITY_NOT_EQUAL) { UnityPrint(UnityStrNotEqual); } + UnityPrintNumberByStyle(threshold, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#define UnityPrintPointlessAndBail() \ +do { \ + UnityTestResultsFailBegin(lineNumber); \ + UnityPrint(UnityStrPointless); \ + UnityAddMsgIfSpecified(msg); \ + UNITY_FAIL_AND_BAIL; \ +} while (0) + +/*-----------------------------------------------*/ +void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + unsigned int length = style & 0xF; + unsigned int increment = 0; + + RETURN_IF_FAIL_OR_IGNORE; + + if (num_elements == 0) + { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while ((elements > 0) && (elements--)) + { + UNITY_INT expect_val; + UNITY_INT actual_val; + + switch (length) + { + case 1: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; + if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) + { + expect_val &= 0x000000FF; + actual_val &= 0x000000FF; + } + increment = sizeof(UNITY_INT8); + break; + + case 2: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; + if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) + { + expect_val &= 0x0000FFFF; + actual_val &= 0x0000FFFF; + } + increment = sizeof(UNITY_INT16); + break; + +#ifdef UNITY_SUPPORT_64 + case 8: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; + increment = sizeof(UNITY_INT64); + break; +#endif + + default: /* default is length 4 bytes */ + case 4: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; +#ifdef UNITY_SUPPORT_64 + if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) + { + expect_val &= 0x00000000FFFFFFFF; + actual_val &= 0x00000000FFFFFFFF; + } +#endif + increment = sizeof(UNITY_INT32); + length = 4; + break; + } + + if (expect_val != actual_val) + { + if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) + { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ + UNITY_INT mask = 1; + mask = (mask << 8 * length) - 1; + expect_val &= mask; + actual_val &= mask; + } + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expect_val, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual_val, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + /* Walk through array by incrementing the pointers */ + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); + } + actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); + } +} + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_FLOAT +/* Wrap this define in a function with variable types as float or double */ +#define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \ + if (UNITY_IS_INF(expected) && UNITY_IS_INF(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ + if (UNITY_NAN_CHECK) return 1; \ + (diff) = (actual) - (expected); \ + if ((diff) < 0) (diff) = -(diff); \ + if ((delta) < 0) (delta) = -(delta); \ + return !(UNITY_IS_NAN(diff) || UNITY_IS_INF(diff) || ((diff) > (delta))) + /* This first part of this condition will catch any NaN or Infinite values */ +#ifndef UNITY_NAN_NOT_EQUAL_NAN + #define UNITY_NAN_CHECK UNITY_IS_NAN(expected) && UNITY_IS_NAN(actual) +#else + #define UNITY_NAN_CHECK 0 +#endif + +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ + do { \ + UnityPrint(UnityStrExpected); \ + UnityPrintFloat(expected); \ + UnityPrint(UnityStrWas); \ + UnityPrintFloat(actual); \ + } while (0) +#else + #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ + UnityPrint(UnityStrDelta) +#endif /* UNITY_EXCLUDE_FLOAT_PRINT */ + +/*-----------------------------------------------*/ +static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOAT actual) +{ + UNITY_FLOAT diff; + UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); +} + +/*-----------------------------------------------*/ +void UnityAssertWithinFloatArray(const UNITY_FLOAT delta, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected; + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual; + UNITY_FLOAT in_delta = delta; + UNITY_FLOAT current_element_delta = delta; + + RETURN_IF_FAIL_OR_IGNORE; + + if (elements == 0) + { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + + if (UNITY_IS_INF(in_delta)) + { + return; /* Arrays will be force equal with infinite delta */ + } + + if (UNITY_IS_NAN(in_delta)) + { + /* Delta must be correct number */ + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + /* fix delta sign if need */ + if (in_delta < 0) + { + in_delta = -in_delta; + } + + while (elements--) + { + current_element_delta = *ptr_expected * UNITY_FLOAT_PRECISION; + + if (current_element_delta < 0) + { + /* fix delta sign for correct calculations */ + current_element_delta = -current_element_delta; + } + + if (!UnityFloatsWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + if (flags == UNITY_ARRAY_TO_ARRAY) + { + ptr_expected++; + } + ptr_actual++; + } +} + +/*-----------------------------------------------*/ +void UnityAssertFloatsWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + + if (!UnityFloatsWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertFloatsNotWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (UnityFloatsWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat((UNITY_DOUBLE)expected); + UnityPrint(UnityStrNotEqual); + UnityPrintFloat((UNITY_DOUBLE)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertGreaterOrLessFloat(const UNITY_FLOAT threshold, + const UNITY_FLOAT actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + int failed; + + RETURN_IF_FAIL_OR_IGNORE; + + failed = 0; + + /* Checking for "not success" rather than failure to get the right result for NaN */ + if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + + if ((compare & UNITY_EQUAL_TO) && UnityFloatsWithin(threshold * UNITY_FLOAT_PRECISION, threshold, actual)) { failed = 0; } + + if (failed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat(actual); + if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } + if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } + if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } + UnityPrintFloat(threshold); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertFloatSpecial(const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style) +{ + const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; + UNITY_INT should_be_trait = ((UNITY_INT)style & 1); + UNITY_INT is_trait = !should_be_trait; + UNITY_INT trait_index = (UNITY_INT)(style >> 1); + + RETURN_IF_FAIL_OR_IGNORE; + + switch (style) + { + case UNITY_FLOAT_IS_INF: + case UNITY_FLOAT_IS_NOT_INF: + is_trait = UNITY_IS_INF(actual) && (actual > 0); + break; + case UNITY_FLOAT_IS_NEG_INF: + case UNITY_FLOAT_IS_NOT_NEG_INF: + is_trait = UNITY_IS_INF(actual) && (actual < 0); + break; + + case UNITY_FLOAT_IS_NAN: + case UNITY_FLOAT_IS_NOT_NAN: + is_trait = UNITY_IS_NAN(actual) ? 1 : 0; + break; + + case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ + case UNITY_FLOAT_IS_NOT_DET: + is_trait = !UNITY_IS_INF(actual) && !UNITY_IS_NAN(actual); + break; + + case UNITY_FLOAT_INVALID_TRAIT: /* Supress warning */ + default: /* including UNITY_FLOAT_INVALID_TRAIT */ + trait_index = 0; + trait_names[0] = UnityStrInvalidFloatTrait; + break; + } + + if (is_trait != should_be_trait) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + if (!should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); + UnityPrint(UnityStrWas); +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + UnityPrintFloat((UNITY_DOUBLE)actual); +#else + if (should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); +#endif + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#endif /* not UNITY_EXCLUDE_FLOAT */ + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_DOUBLE +static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_DOUBLE actual) +{ + UNITY_DOUBLE diff; + UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); +} + +/*-----------------------------------------------*/ +void UnityAssertWithinDoubleArray(const UNITY_DOUBLE delta, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected; + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual; + UNITY_DOUBLE in_delta = delta; + UNITY_DOUBLE current_element_delta = delta; + + RETURN_IF_FAIL_OR_IGNORE; + + if (elements == 0) + { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + + if (UNITY_IS_INF(in_delta)) + { + return; /* Arrays will be force equal with infinite delta */ + } + + if (UNITY_IS_NAN(in_delta)) + { + /* Delta must be correct number */ + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + /* fix delta sign if need */ + if (in_delta < 0) + { + in_delta = -in_delta; + } + + while (elements--) + { + current_element_delta = *ptr_expected * UNITY_DOUBLE_PRECISION; + + if (current_element_delta < 0) + { + /* fix delta sign for correct calculations */ + current_element_delta = -current_element_delta; + } + + if (!UnityDoublesWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + if (flags == UNITY_ARRAY_TO_ARRAY) + { + ptr_expected++; + } + ptr_actual++; + } +} + +/*-----------------------------------------------*/ +void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (!UnityDoublesWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertDoublesNotWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (UnityDoublesWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat((UNITY_DOUBLE)expected); + UnityPrint(UnityStrNotEqual); + UnityPrintFloat((UNITY_DOUBLE)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertGreaterOrLessDouble(const UNITY_DOUBLE threshold, + const UNITY_DOUBLE actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + int failed; + + RETURN_IF_FAIL_OR_IGNORE; + + failed = 0; + + /* Checking for "not success" rather than failure to get the right result for NaN */ + if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + + if ((compare & UNITY_EQUAL_TO) && UnityDoublesWithin(threshold * UNITY_DOUBLE_PRECISION, threshold, actual)) { failed = 0; } + + if (failed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat(actual); + if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } + if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } + if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } + UnityPrintFloat(threshold); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style) +{ + const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; + UNITY_INT should_be_trait = ((UNITY_INT)style & 1); + UNITY_INT is_trait = !should_be_trait; + UNITY_INT trait_index = (UNITY_INT)(style >> 1); + + RETURN_IF_FAIL_OR_IGNORE; + + switch (style) + { + case UNITY_FLOAT_IS_INF: + case UNITY_FLOAT_IS_NOT_INF: + is_trait = UNITY_IS_INF(actual) && (actual > 0); + break; + case UNITY_FLOAT_IS_NEG_INF: + case UNITY_FLOAT_IS_NOT_NEG_INF: + is_trait = UNITY_IS_INF(actual) && (actual < 0); + break; + + case UNITY_FLOAT_IS_NAN: + case UNITY_FLOAT_IS_NOT_NAN: + is_trait = UNITY_IS_NAN(actual) ? 1 : 0; + break; + + case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ + case UNITY_FLOAT_IS_NOT_DET: + is_trait = !UNITY_IS_INF(actual) && !UNITY_IS_NAN(actual); + break; + + case UNITY_FLOAT_INVALID_TRAIT: /* Supress warning */ + default: /* including UNITY_FLOAT_INVALID_TRAIT */ + trait_index = 0; + trait_names[0] = UnityStrInvalidFloatTrait; + break; + } + + if (is_trait != should_be_trait) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + if (!should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); + UnityPrint(UnityStrWas); +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + UnityPrintFloat(actual); +#else + if (should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); +#endif + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#endif /* not UNITY_EXCLUDE_DOUBLE */ + +/*-----------------------------------------------*/ +void UnityAssertNumbersWithin(const UNITY_UINT delta, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (actual > expected) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); + } + } + else + { + if ((UNITY_UINT)actual > (UNITY_UINT)expected) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrDelta); + UnityPrintNumberByStyle((UNITY_INT)delta, style); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expected, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, + UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + unsigned int length = style & 0xF; + unsigned int increment = 0; + + RETURN_IF_FAIL_OR_IGNORE; + + if (num_elements == 0) + { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while ((elements > 0) && (elements--)) + { + UNITY_INT expect_val; + UNITY_INT actual_val; + + switch (length) + { + case 1: + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; + increment = sizeof(UNITY_INT8); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)actual; + increment = sizeof(UNITY_UINT8); + } + break; + + case 2: + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; + increment = sizeof(UNITY_INT16); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)actual; + increment = sizeof(UNITY_UINT16); + } + break; + +#ifdef UNITY_SUPPORT_64 + case 8: + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; + increment = sizeof(UNITY_INT64); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)actual; + increment = sizeof(UNITY_UINT64); + } + break; +#endif + + default: /* default is length 4 bytes */ + case 4: + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; + increment = sizeof(UNITY_INT32); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)actual; + increment = sizeof(UNITY_UINT32); + } + length = 4; + break; + } + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (actual_val > expect_val) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); + } + } + else + { + if ((UNITY_UINT)actual_val > (UNITY_UINT)expect_val) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); + } + } + + if (Unity.CurrentTestFailed) + { + if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) + { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ + UNITY_INT mask = 1; + mask = (mask << 8 * length) - 1; + expect_val &= mask; + actual_val &= mask; + } + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrDelta); + UnityPrintNumberByStyle((UNITY_INT)delta, style); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expect_val, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual_val, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + /* Walk through array by incrementing the pointers */ + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); + } + actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualString(const char* expected, + const char* actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + UNITY_UINT32 i; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if both pointers not null compare the strings */ + if (expected && actual) + { + for (i = 0; expected[i] || actual[i]; i++) + { + if (expected[i] != actual[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* fail if either null but not if both */ + if (expected || actual) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrintExpectedAndActualStrings(expected, actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualStringLen(const char* expected, + const char* actual, + const UNITY_UINT32 length, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + UNITY_UINT32 i; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if both pointers not null compare the strings */ + if (expected && actual) + { + for (i = 0; (i < length) && (expected[i] || actual[i]); i++) + { + if (expected[i] != actual[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* fail if either null but not if both */ + if (expected || actual) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrintExpectedAndActualStringsLen(expected, actual, length); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected, + const char** actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 i = 0; + UNITY_UINT32 j = 0; + const char* expd = NULL; + const char* act = NULL; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if no elements, it's an error */ + if (num_elements == 0) + { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + + if ((const void*)expected == (const void*)actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + if (flags != UNITY_ARRAY_TO_ARRAY) + { + expd = (const char*)expected; + } + + do + { + act = actual[j]; + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expd = ((const char* const*)expected)[j]; + } + + /* if both pointers not null compare the strings */ + if (expd && act) + { + for (i = 0; expd[i] || act[i]; i++) + { + if (expd[i] != act[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* handle case of one pointers being null (if both null, test should pass) */ + if (expd != act) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + if (num_elements > 1) + { + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(j); + } + UnityPrintExpectedAndActualStrings(expd, act); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + } while (++j < num_elements); +} + +/*-----------------------------------------------*/ +void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 length, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_PTR_ATTRIBUTE const unsigned char* ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; + UNITY_PTR_ATTRIBUTE const unsigned char* ptr_act = (UNITY_PTR_ATTRIBUTE const unsigned char*)actual; + UNITY_UINT32 elements = num_elements; + UNITY_UINT32 bytes; + + RETURN_IF_FAIL_OR_IGNORE; + + if (elements == 0) + { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + if (length == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while (elements--) + { + bytes = length; + while (bytes--) + { + if (*ptr_exp != *ptr_act) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrMemory); + if (num_elements > 1) + { + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + } + UnityPrint(UnityStrByte); + UnityPrintNumberUnsigned(length - bytes - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(*ptr_exp, UNITY_DISPLAY_STYLE_HEX8); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(*ptr_act, UNITY_DISPLAY_STYLE_HEX8); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + ptr_exp++; + ptr_act++; + } + if (flags == UNITY_ARRAY_TO_VAL) + { + ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; + } + } +} + +/*-----------------------------------------------*/ + +static union +{ + UNITY_INT8 i8; + UNITY_INT16 i16; + UNITY_INT32 i32; +#ifdef UNITY_SUPPORT_64 + UNITY_INT64 i64; +#endif +#ifndef UNITY_EXCLUDE_FLOAT + float f; +#endif +#ifndef UNITY_EXCLUDE_DOUBLE + double d; +#endif +} UnityQuickCompare; + +UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size) +{ + switch(size) + { + case 1: + UnityQuickCompare.i8 = (UNITY_INT8)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i8); + + case 2: + UnityQuickCompare.i16 = (UNITY_INT16)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i16); + +#ifdef UNITY_SUPPORT_64 + case 8: + UnityQuickCompare.i64 = (UNITY_INT64)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i64); +#endif + + default: /* 4 bytes */ + UnityQuickCompare.i32 = (UNITY_INT32)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i32); + } +} + +#ifndef UNITY_EXCLUDE_FLOAT +/*-----------------------------------------------*/ +UNITY_INTERNAL_PTR UnityFloatToPtr(const float num) +{ + UnityQuickCompare.f = num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.f); +} +#endif + +#ifndef UNITY_EXCLUDE_DOUBLE +/*-----------------------------------------------*/ +UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num) +{ + UnityQuickCompare.d = num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.d); +} +#endif + +#ifdef UNITY_INCLUDE_PRINT_FORMATTED + +/*----------------------------------------------- + * printf length modifier helpers + *-----------------------------------------------*/ + +enum UnityLengthModifier { + UNITY_LENGTH_MODIFIER_NONE, + UNITY_LENGTH_MODIFIER_LONG_LONG, + UNITY_LENGTH_MODIFIER_LONG, +}; + +#define UNITY_EXTRACT_ARG(NUMBER_T, NUMBER, LENGTH_MOD, VA, ARG_T) \ +do { \ + switch (LENGTH_MOD) \ + { \ + case UNITY_LENGTH_MODIFIER_LONG_LONG: \ + { \ + NUMBER = (NUMBER_T)va_arg(VA, long long ARG_T); \ + break; \ + } \ + case UNITY_LENGTH_MODIFIER_LONG: \ + { \ + NUMBER = (NUMBER_T)va_arg(VA, long ARG_T); \ + break; \ + } \ + case UNITY_LENGTH_MODIFIER_NONE: \ + default: \ + { \ + NUMBER = (NUMBER_T)va_arg(VA, ARG_T); \ + break; \ + } \ + } \ +} while (0) + +static enum UnityLengthModifier UnityLengthModifierGet(const char *pch, int *length) +{ + enum UnityLengthModifier length_mod; + switch (pch[0]) + { + case 'l': + { + if (pch[1] == 'l') + { + *length = 2; + length_mod = UNITY_LENGTH_MODIFIER_LONG_LONG; + } + else + { + *length = 1; + length_mod = UNITY_LENGTH_MODIFIER_LONG; + } + break; + } + case 'h': + { + // short and char are converted to int + length_mod = UNITY_LENGTH_MODIFIER_NONE; + if (pch[1] == 'h') + { + *length = 2; + } + else + { + *length = 1; + } + break; + } + case 'j': + case 'z': + case 't': + case 'L': + { + // Not supported, but should gobble up the length specifier anyway + length_mod = UNITY_LENGTH_MODIFIER_NONE; + *length = 1; + break; + } + default: + { + length_mod = UNITY_LENGTH_MODIFIER_NONE; + *length = 0; + } + } + return length_mod; +} + +/*----------------------------------------------- + * printf helper function + *-----------------------------------------------*/ +static void UnityPrintFVA(const char* format, va_list va) +{ + const char* pch = format; + if (pch != NULL) + { + while (*pch) + { + /* format identification character */ + if (*pch == '%') + { + pch++; + + if (pch != NULL) + { + int length_mod_size; + enum UnityLengthModifier length_mod = UnityLengthModifierGet(pch, &length_mod_size); + pch += length_mod_size; + + switch (*pch) + { + case 'd': + case 'i': + { + UNITY_INT number; + UNITY_EXTRACT_ARG(UNITY_INT, number, length_mod, va, int); + UnityPrintNumber((UNITY_INT)number); + break; + } +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + case 'f': + case 'g': + { + const double number = va_arg(va, double); + UnityPrintFloat((UNITY_DOUBLE)number); + break; + } +#endif + case 'u': + { + UNITY_UINT number; + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); + UnityPrintNumberUnsigned(number); + break; + } + case 'b': + { + UNITY_UINT number; + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); + const UNITY_UINT mask = (UNITY_UINT)0 - (UNITY_UINT)1; + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('b'); + UnityPrintMask(mask, number); + break; + } + case 'x': + case 'X': + { + UNITY_UINT number; + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex(number, UNITY_MAX_NIBBLES); + break; + } + case 'p': + { + UNITY_UINT number; + char nibbles_to_print = 8; + if (UNITY_POINTER_WIDTH == 64) + { + length_mod = UNITY_LENGTH_MODIFIER_LONG_LONG; + nibbles_to_print = 16; + } + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)number, nibbles_to_print); + break; + } + case 'c': + { + const int ch = va_arg(va, int); + UnityPrintChar((const char *)&ch); + break; + } + case 's': + { + const char * string = va_arg(va, const char *); + UnityPrint(string); + break; + } + case '%': + { + UnityPrintChar(pch); + break; + } + default: + { + /* print the unknown format character */ + UNITY_OUTPUT_CHAR('%'); + UnityPrintChar(pch); + break; + } + } + } + } +#ifdef UNITY_OUTPUT_COLOR + /* print ANSI escape code */ + else if ((*pch == 27) && (*(pch + 1) == '[')) + { + pch += UnityPrintAnsiEscapeString(pch); + continue; + } +#endif + else if (*pch == '\n') + { + UNITY_PRINT_EOL(); + } + else + { + UnityPrintChar(pch); + } + + pch++; + } + } +} + +void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...) +{ + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint("INFO"); + if(format != NULL) + { + UnityPrint(": "); + va_list va; + va_start(va, format); + UnityPrintFVA(format, va); + va_end(va); + } + UNITY_PRINT_EOL(); +} +#endif /* ! UNITY_INCLUDE_PRINT_FORMATTED */ + + +/*----------------------------------------------- + * Control Functions + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +void UnityFail(const char* msg, const UNITY_LINE_TYPE line) +{ + RETURN_IF_FAIL_OR_IGNORE; + + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrFail); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + +#ifdef UNITY_PRINT_TEST_CONTEXT + UNITY_PRINT_TEST_CONTEXT(); +#endif +#ifndef UNITY_EXCLUDE_DETAILS + if (Unity.CurrentDetail1) + { + UnityPrint(UnityStrDetail1Name); + UnityPrint(Unity.CurrentDetail1); + if (Unity.CurrentDetail2) + { + UnityPrint(UnityStrDetail2Name); + UnityPrint(Unity.CurrentDetail2); + } + UnityPrint(UnityStrSpacer); + } +#endif + if (msg[0] != ' ') + { + UNITY_OUTPUT_CHAR(' '); + } + UnityPrint(msg); + } + + UNITY_FAIL_AND_BAIL; +} + +/*-----------------------------------------------*/ +void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line) +{ + RETURN_IF_FAIL_OR_IGNORE; + + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrIgnore); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(msg); + } + UNITY_IGNORE_AND_BAIL; +} + +/*-----------------------------------------------*/ +void UnityMessage(const char* msg, const UNITY_LINE_TYPE line) +{ + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint("INFO"); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(msg); + } + UNITY_PRINT_EOL(); +} + +/*-----------------------------------------------*/ +/* If we have not defined our own test runner, then include our default test runner to make life easier */ +#ifndef UNITY_SKIP_DEFAULT_RUNNER +void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum) +{ + Unity.CurrentTestName = FuncName; + Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum; + Unity.NumberOfTests++; + UNITY_CLR_DETAILS(); + UNITY_EXEC_TIME_START(); + if (TEST_PROTECT()) + { + setUp(); + Func(); + } + if (TEST_PROTECT()) + { + tearDown(); + } + UNITY_EXEC_TIME_STOP(); + UnityConcludeTest(); +} +#endif + +/*-----------------------------------------------*/ +void UnitySetTestFile(const char* filename) +{ + Unity.TestFile = filename; +} + +/*-----------------------------------------------*/ +void UnityBegin(const char* filename) +{ + Unity.TestFile = filename; + Unity.CurrentTestName = NULL; + Unity.CurrentTestLineNumber = 0; + Unity.NumberOfTests = 0; + Unity.TestFailures = 0; + Unity.TestIgnores = 0; + Unity.CurrentTestFailed = 0; + Unity.CurrentTestIgnored = 0; + + UNITY_CLR_DETAILS(); + UNITY_OUTPUT_START(); +} + +/*-----------------------------------------------*/ +int UnityEnd(void) +{ + UNITY_PRINT_EOL(); + UnityPrint(UnityStrBreaker); + UNITY_PRINT_EOL(); + UnityPrintNumber((UNITY_INT)(Unity.NumberOfTests)); + UnityPrint(UnityStrResultsTests); + UnityPrintNumber((UNITY_INT)(Unity.TestFailures)); + UnityPrint(UnityStrResultsFailures); + UnityPrintNumber((UNITY_INT)(Unity.TestIgnores)); + UnityPrint(UnityStrResultsIgnored); + UNITY_PRINT_EOL(); + if (Unity.TestFailures == 0U) + { + UnityPrint(UnityStrOk); + } + else + { + UnityPrint(UnityStrFail); +#ifdef UNITY_DIFFERENTIATE_FINAL_FAIL + UNITY_OUTPUT_CHAR('E'); UNITY_OUTPUT_CHAR('D'); +#endif + } + UNITY_PRINT_EOL(); + UNITY_FLUSH_CALL(); + UNITY_OUTPUT_COMPLETE(); + return (int)(Unity.TestFailures); +} + +/*----------------------------------------------- + * Command Line Argument Support + *-----------------------------------------------*/ +#ifdef UNITY_USE_COMMAND_LINE_ARGS + +char* UnityOptionIncludeNamed = NULL; +char* UnityOptionExcludeNamed = NULL; +int UnityVerbosity = 1; +int UnityStrictMatch = 0; + +/*-----------------------------------------------*/ +int UnityParseOptions(int argc, char** argv) +{ + int i; + UnityOptionIncludeNamed = NULL; + UnityOptionExcludeNamed = NULL; + UnityStrictMatch = 0; + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'l': /* list tests */ + return -1; + case 'n': /* include tests with name including this string */ + case 'f': /* an alias for -n */ + UnityStrictMatch = (argv[i][1] == 'n'); /* strictly match this string if -n */ + if (argv[i][2] == '=') + { + UnityOptionIncludeNamed = &argv[i][3]; + } + else if (++i < argc) + { + UnityOptionIncludeNamed = argv[i]; + } + else + { + UnityPrint("ERROR: No Test String to Include Matches For"); + UNITY_PRINT_EOL(); + return 1; + } + break; + case 'q': /* quiet */ + UnityVerbosity = 0; + break; + case 'v': /* verbose */ + UnityVerbosity = 2; + break; + case 'x': /* exclude tests with name including this string */ + if (argv[i][2] == '=') + { + UnityOptionExcludeNamed = &argv[i][3]; + } + else if (++i < argc) + { + UnityOptionExcludeNamed = argv[i]; + } + else + { + UnityPrint("ERROR: No Test String to Exclude Matches For"); + UNITY_PRINT_EOL(); + return 1; + } + break; + default: + UnityPrint("ERROR: Unknown Option "); + UNITY_OUTPUT_CHAR(argv[i][1]); + UNITY_PRINT_EOL(); + /* Now display help */ + /* FALLTHRU */ + case 'h': + UnityPrint("Options: "); UNITY_PRINT_EOL(); + UnityPrint("-l List all tests and exit"); UNITY_PRINT_EOL(); + UnityPrint("-f NAME Filter to run only tests whose name includes NAME"); UNITY_PRINT_EOL(); + UnityPrint("-n NAME Run only the test named NAME"); UNITY_PRINT_EOL(); + UnityPrint("-h show this Help menu"); UNITY_PRINT_EOL(); + UnityPrint("-q Quiet/decrease verbosity"); UNITY_PRINT_EOL(); + UnityPrint("-v increase Verbosity"); UNITY_PRINT_EOL(); + UnityPrint("-x NAME eXclude tests whose name includes NAME"); UNITY_PRINT_EOL(); + UNITY_OUTPUT_FLUSH(); + return 1; + } + } + } + + return 0; +} + +/*-----------------------------------------------*/ +static int IsStringInBiggerString(const char* longstring, const char* shortstring) +{ + const char* lptr = longstring; + const char* sptr = shortstring; + const char* lnext = lptr; + + if (*sptr == '*') + { + return UnityStrictMatch ? 0 : 1; + } + + while (*lptr) + { + lnext = lptr + 1; + + /* If they current bytes match, go on to the next bytes */ + while (*lptr && *sptr && (*lptr == *sptr)) + { + lptr++; + sptr++; + + switch (*sptr) + { + case '*': /* we encountered a wild-card */ + return UnityStrictMatch ? 0 : 1; + + case ',': /* we encountered the end of match string */ + case '"': + case '\'': + case 0: + return (!UnityStrictMatch || (*lptr == 0)) ? 1 : 0; + + case ':': /* we encountered the end of a partial match */ + return 2; + + default: + break; + } + } + + // If we didn't match and we're on strict matching, we already know we failed + if (UnityStrictMatch) + { + return 0; + } + + /* Otherwise we start in the long pointer 1 character further and try again */ + lptr = lnext; + sptr = shortstring; + } + + return 0; +} + +/*-----------------------------------------------*/ +static int UnityStringArgumentMatches(const char* str) +{ + int retval; + const char* ptr1; + const char* ptr2; + const char* ptrf; + + /* Go through the options and get the substrings for matching one at a time */ + ptr1 = str; + while (ptr1[0] != 0) + { + if ((ptr1[0] == '"') || (ptr1[0] == '\'')) + { + ptr1++; + } + + /* look for the start of the next partial */ + ptr2 = ptr1; + ptrf = 0; + do + { + ptr2++; + if ((ptr2[0] == ':') && (ptr2[1] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')) + { + ptrf = &ptr2[1]; + } + } while ((ptr2[0] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')); + + while ((ptr2[0] != 0) && ((ptr2[0] == ':') || (ptr2[0] == '\'') || (ptr2[0] == '"') || (ptr2[0] == ','))) + { + ptr2++; + } + + /* done if complete filename match */ + retval = IsStringInBiggerString(Unity.TestFile, ptr1); + if (retval == 1) + { + return retval; + } + + /* done if testname match after filename partial match */ + if ((retval == 2) && (ptrf != 0)) + { + if (IsStringInBiggerString(Unity.CurrentTestName, ptrf)) + { + return 1; + } + } + + /* done if complete testname match */ + if (IsStringInBiggerString(Unity.CurrentTestName, ptr1) == 1) + { + return 1; + } + + ptr1 = ptr2; + } + + /* we couldn't find a match for any substrings */ + return 0; +} + +/*-----------------------------------------------*/ +int UnityTestMatches(void) +{ + /* Check if this test name matches the included test pattern */ + int retval; + if (UnityOptionIncludeNamed) + { + retval = UnityStringArgumentMatches(UnityOptionIncludeNamed); + } + else + { + retval = 1; + } + + /* Check if this test name matches the excluded test pattern */ + if (UnityOptionExcludeNamed) + { + if (UnityStringArgumentMatches(UnityOptionExcludeNamed)) + { + retval = 0; + } + } + + return retval; +} + +#endif /* UNITY_USE_COMMAND_LINE_ARGS */ +/*-----------------------------------------------*/ diff --git a/src/worker/test/unity.h b/src/worker/test/unity.h new file mode 100644 index 00000000..9e2a97b7 --- /dev/null +++ b/src/worker/test/unity.h @@ -0,0 +1,698 @@ +/* ========================================================================= + Unity - A Test Framework for C + ThrowTheSwitch.org + Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef UNITY_FRAMEWORK_H +#define UNITY_FRAMEWORK_H +#define UNITY + +#define UNITY_VERSION_MAJOR 2 +#define UNITY_VERSION_MINOR 6 +#define UNITY_VERSION_BUILD 1 +#define UNITY_VERSION ((UNITY_VERSION_MAJOR << 16) | (UNITY_VERSION_MINOR << 8) | UNITY_VERSION_BUILD) + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "unity_internals.h" + +/*------------------------------------------------------- + * Test Setup / Teardown + *-------------------------------------------------------*/ + +/* These functions are intended to be called before and after each test. + * If using unity directly, these will need to be provided for each test + * executable built. If you are using the test runner generator and/or + * Ceedling, these are optional. */ +void setUp(void); +void tearDown(void); + +/* These functions are intended to be called at the beginning and end of an + * entire test suite. suiteTearDown() is passed the number of tests that + * failed, and its return value becomes the exit code of main(). If using + * Unity directly, you're in charge of calling these if they are desired. + * If using Ceedling or the test runner generator, these will be called + * automatically if they exist. */ +void suiteSetUp(void); +int suiteTearDown(int num_failures); + +/*------------------------------------------------------- + * Test Reset and Verify + *-------------------------------------------------------*/ + +/* These functions are intended to be called before during tests in order + * to support complex test loops, etc. Both are NOT built into Unity. Instead + * the test runner generator will create them. resetTest will run teardown and + * setup again, verifying any end-of-test needs between. verifyTest will only + * run the verification. */ +void resetTest(void); +void verifyTest(void); + +/*------------------------------------------------------- + * Configuration Options + *------------------------------------------------------- + * All options described below should be passed as a compiler flag to all files using Unity. If you must add #defines, place them BEFORE the #include above. + + * Integers/longs/pointers + * - Unity attempts to automatically discover your integer sizes + * - define UNITY_EXCLUDE_STDINT_H to stop attempting to look in + * - define UNITY_EXCLUDE_LIMITS_H to stop attempting to look in + * - If you cannot use the automatic methods above, you can force Unity by using these options: + * - define UNITY_SUPPORT_64 + * - set UNITY_INT_WIDTH + * - set UNITY_LONG_WIDTH + * - set UNITY_POINTER_WIDTH + + * Floats + * - define UNITY_EXCLUDE_FLOAT to disallow floating point comparisons + * - define UNITY_FLOAT_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_FLOAT + * - define UNITY_FLOAT_TYPE to specify doubles instead of single precision floats + * - define UNITY_INCLUDE_DOUBLE to allow double floating point comparisons + * - define UNITY_EXCLUDE_DOUBLE to disallow double floating point comparisons (default) + * - define UNITY_DOUBLE_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_DOUBLE + * - define UNITY_DOUBLE_TYPE to specify something other than double + * - define UNITY_EXCLUDE_FLOAT_PRINT to trim binary size, won't print floating point values in errors + + * Output + * - by default, Unity prints to standard out with putchar. define UNITY_OUTPUT_CHAR(a) with a different function if desired + * - define UNITY_DIFFERENTIATE_FINAL_FAIL to print FAILED (vs. FAIL) at test end summary - for automated search for failure + + * Optimization + * - by default, line numbers are stored in unsigned shorts. Define UNITY_LINE_TYPE with a different type if your files are huge + * - by default, test and failure counters are unsigned shorts. Define UNITY_COUNTER_TYPE with a different type if you want to save space or have more than 65535 Tests. + + * Test Cases + * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script + + * Parameterized Tests + * - you'll want to create a define of TEST_CASE(...), TEST_RANGE(...) and/or TEST_MATRIX(...) which basically evaluates to nothing + + * Tests with Arguments + * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity + + *------------------------------------------------------- + * Basic Fail and Ignore + *-------------------------------------------------------*/ + +#define TEST_FAIL_MESSAGE(message) UNITY_TEST_FAIL(__LINE__, (message)) +#define TEST_FAIL() UNITY_TEST_FAIL(__LINE__, NULL) +#define TEST_IGNORE_MESSAGE(message) UNITY_TEST_IGNORE(__LINE__, (message)) +#define TEST_IGNORE() UNITY_TEST_IGNORE(__LINE__, NULL) +#define TEST_MESSAGE(message) UnityMessage((message), __LINE__) +#define TEST_ONLY() +#ifdef UNITY_INCLUDE_PRINT_FORMATTED +#define TEST_PRINTF(message, ...) UnityPrintF(__LINE__, (message), ##__VA_ARGS__) +#endif + +/* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails. + * This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */ +#define TEST_PASS() TEST_ABORT() +#define TEST_PASS_MESSAGE(message) do { UnityMessage((message), __LINE__); TEST_ABORT(); } while (0) + +/*------------------------------------------------------- + * Build Directives + *------------------------------------------------------- + + * These macros do nothing, but they are useful for additional build context. + * Tools (like Ceedling) can scan for these directives and make use of them for + * per-test-executable #include search paths and linking. */ + +/* Add source files to a test executable's compilation and linking. Ex: TEST_SOURCE_FILE("sandwiches.c") */ +#define TEST_SOURCE_FILE(a) + +/* Customize #include search paths for a test executable's compilation. Ex: TEST_INCLUDE_PATH("src/module_a/inc") */ +#define TEST_INCLUDE_PATH(a) + +/*------------------------------------------------------- + * Test Asserts (simple) + *-------------------------------------------------------*/ + +/* Boolean */ +#define TEST_ASSERT(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expression Evaluated To FALSE") +#define TEST_ASSERT_TRUE(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expected TRUE Was FALSE") +#define TEST_ASSERT_UNLESS(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expression Evaluated To TRUE") +#define TEST_ASSERT_FALSE(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expected FALSE Was TRUE") +#define TEST_ASSERT_NULL(pointer) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, " Expected NULL") +#define TEST_ASSERT_NOT_NULL(pointer) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, " Expected Non-NULL") +#define TEST_ASSERT_EMPTY(pointer) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, " Expected Empty") +#define TEST_ASSERT_NOT_EMPTY(pointer) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, " Expected Non-Empty") + +/* Integers (of all sizes) */ +#define TEST_ASSERT_EQUAL_INT(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_size_t(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX8(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX16(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX32(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX64(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_CHAR(expected, actual) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS(mask, expected, actual) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS_HIGH(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(-1), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS_LOW(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(0), (actual), __LINE__, NULL) +#define TEST_ASSERT_BIT_HIGH(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(-1), (actual), __LINE__, NULL) +#define TEST_ASSERT_BIT_LOW(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(0), (actual), __LINE__, NULL) + +/* Integer Not Equal To (of all sizes) */ +#define TEST_ASSERT_NOT_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) + +/* Integer Greater Than/ Less Than (of all sizes) */ +#define TEST_ASSERT_GREATER_THAN(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_LESS_THAN(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_GREATER_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_LESS_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) + +/* Integer Ranges (of all sizes) */ +#define TEST_ASSERT_INT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_size_t_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_CHAR_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, NULL) + +/* Integer Array Ranges (of all sizes) */ +#define TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_size_t_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) + + +/* Structs and Strings */ +#define TEST_ASSERT_EQUAL_PTR(expected, actual) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING(expected, actual) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, NULL) + +/* Arrays */ +#define TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_size_t_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) + +/* Arrays Compared To Single Value */ +#define TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_size_t(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, NULL) + +/* Floating Point (If Enabled) */ +#define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_FLOAT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual) UNITY_TEST_ASSERT_LESS_THAN_FLOAT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual) UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, NULL) + +/* Double (If Enabled) */ +#define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_LESS_THAN_DOUBLE((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, NULL) + +/* Shorthand */ +#ifdef UNITY_SHORTHAND_AS_OLD +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") +#endif +#ifdef UNITY_SHORTHAND_AS_INT +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_MEM +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_RAW +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, " Expected Equal") +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") +#endif +#ifdef UNITY_SHORTHAND_AS_NONE +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif + +/*------------------------------------------------------- + * Test Asserts (with additional messages) + *-------------------------------------------------------*/ + +/* Boolean */ +#define TEST_ASSERT_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) +#define TEST_ASSERT_TRUE_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) +#define TEST_ASSERT_UNLESS_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) +#define TEST_ASSERT_FALSE_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) +#define TEST_ASSERT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, (message)) +#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, (message)) +#define TEST_ASSERT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, (message)) +#define TEST_ASSERT_NOT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, (message)) + +/* Integers (of all sizes) */ +#define TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_size_t_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_MESSAGE(mask, expected, actual, message) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_HIGH_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_LOW_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, (message)) +#define TEST_ASSERT_BIT_HIGH_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) +#define TEST_ASSERT_BIT_LOW_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_CHAR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, (message)) + +/* Integer Not Equal To (of all sizes) */ +#define TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) + + +/* Integer Greater Than/ Less Than (of all sizes) */ +#define TEST_ASSERT_GREATER_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_LESS_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) + +/* Integer Ranges (of all sizes) */ +#define TEST_ASSERT_INT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_size_t_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_CHAR_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, (message)) + +/* Integer Array Ranges (of all sizes) */ +#define TEST_ASSERT_INT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_size_t_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_CHAR_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) + + +/* Structs and Strings */ +#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, (message)) + +/* Arrays */ +#define TEST_ASSERT_EQUAL_INT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_size_t_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_PTR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_MEMORY_ARRAY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_CHAR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) + +/* Arrays Compared To Single Value*/ +#define TEST_ASSERT_EACH_EQUAL_INT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_size_t_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_PTR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_STRING_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_MEMORY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_CHAR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, (message)) + +/* Floating Point (If Enabled) */ +#define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_FLOAT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_THAN_FLOAT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, (message)) + +/* Double (If Enabled) */ +#define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_THAN_DOUBLE((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, (message)) + +/* Shorthand */ +#ifdef UNITY_SHORTHAND_AS_OLD +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, (message)) +#endif +#ifdef UNITY_SHORTHAND_AS_INT +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, message) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_MEM +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, message) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_RAW +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, message) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, message) +#endif +#ifdef UNITY_SHORTHAND_AS_NONE +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif + +/* end of UNITY_FRAMEWORK_H */ +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/worker/test/unity_internals.h b/src/worker/test/unity_internals.h new file mode 100644 index 00000000..562f5b6d --- /dev/null +++ b/src/worker/test/unity_internals.h @@ -0,0 +1,1183 @@ +/* ========================================================================= + Unity - A Test Framework for C + ThrowTheSwitch.org + Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef UNITY_INTERNALS_H +#define UNITY_INTERNALS_H + +#ifdef UNITY_INCLUDE_CONFIG_H +#include "unity_config.h" +#endif + +#ifndef UNITY_EXCLUDE_SETJMP_H +#include +#endif + +#ifndef UNITY_EXCLUDE_MATH_H +#include +#endif + +#ifndef UNITY_EXCLUDE_STDDEF_H +#include +#endif + +#ifdef UNITY_INCLUDE_PRINT_FORMATTED +#include +#endif + +/* Unity Attempts to Auto-Detect Integer Types + * Attempt 1: UINT_MAX, ULONG_MAX in , or default to 32 bits + * Attempt 2: UINTPTR_MAX in , or default to same size as long + * The user may override any of these derived constants: + * UNITY_INT_WIDTH, UNITY_LONG_WIDTH, UNITY_POINTER_WIDTH */ +#ifndef UNITY_EXCLUDE_STDINT_H +#include +#endif + +#ifndef UNITY_EXCLUDE_LIMITS_H +#include +#endif + +#if defined(__GNUC__) || defined(__clang__) + #define UNITY_FUNCTION_ATTR(a) __attribute__((a)) +#else + #define UNITY_FUNCTION_ATTR(a) /* ignore */ +#endif + +/* UNITY_NORETURN is only required if we have setjmp.h. */ +#ifndef UNITY_EXCLUDE_SETJMP_H + #ifndef UNITY_NORETURN + #if defined(__cplusplus) + #if __cplusplus >= 201103L + #define UNITY_NORETURN [[ noreturn ]] + #endif + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && __STDC_VERSION__ < 202311L + /* _Noreturn keyword is used from C11 but deprecated in C23. */ + #if defined(_WIN32) && defined(_MSC_VER) + /* We are using MSVC compiler on Windows platform. */ + /* Not all Windows SDKs supports , but compiler can support C11: */ + /* https://devblogs.microsoft.com/cppblog/c11-and-c17-standard-support-arriving-in-msvc/ */ + /* Not sure, that Mingw compilers has Windows SDK headers at all. */ + #include + #endif + + /* Using Windows SDK predefined macro for detecting supported SDK with MSVC compiler. */ + /* Mingw GCC should work without that fixes. */ + /* Based on: */ + /* https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=msvc-170 */ + /* NTDDI_WIN10_FE is equal to Windows 10 SDK 2104 */ + #if defined(_MSC_VER) && ((!defined(NTDDI_WIN10_FE)) || WDK_NTDDI_VERSION < NTDDI_WIN10_FE) + /* Based on tests and: */ + /* https://docs.microsoft.com/en-us/cpp/c-language/noreturn?view=msvc-170 */ + /* https://en.cppreference.com/w/c/language/_Noreturn */ + #define UNITY_NORETURN _Noreturn + #else /* Using newer Windows SDK or not MSVC compiler */ + #if defined(__GNUC__) + /* The header collides with __attribute(noreturn)__ from GCC. */ + #define UNITY_NORETURN _Noreturn + #else + #include + #define UNITY_NORETURN noreturn + #endif + #endif + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L + /* Since C23, the keyword _Noreturn has been replaced by the attribute noreturn, based on: */ + /* https://en.cppreference.com/w/c/language/attributes/noreturn */ + #define UNITY_NORETURN [[ noreturn ]] + #endif + #endif + #ifndef UNITY_NORETURN + #define UNITY_NORETURN UNITY_FUNCTION_ATTR(__noreturn__) + #endif +#endif + +/*------------------------------------------------------- + * Guess Widths If Not Specified + *-------------------------------------------------------*/ + +/* Determine the size of an int, if not already specified. + * We cannot use sizeof(int), because it is not yet defined + * at this stage in the translation of the C program. + * Also sizeof(int) does return the size in addressable units on all platforms, + * which may not necessarily be the size in bytes. + * Therefore, infer it from UINT_MAX if possible. */ +#ifndef UNITY_INT_WIDTH + #ifdef UINT_MAX + #if (UINT_MAX == 0xFFFF) + #define UNITY_INT_WIDTH (16) + #elif (UINT_MAX == 0xFFFFFFFF) + #define UNITY_INT_WIDTH (32) + #elif (UINT_MAX == 0xFFFFFFFFFFFFFFFF) + #define UNITY_INT_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_INT_WIDTH (32) + #endif /* UINT_MAX */ +#endif + +/* Determine the size of a long, if not already specified. */ +#ifndef UNITY_LONG_WIDTH + #ifdef ULONG_MAX + #if (ULONG_MAX == 0xFFFF) + #define UNITY_LONG_WIDTH (16) + #elif (ULONG_MAX == 0xFFFFFFFF) + #define UNITY_LONG_WIDTH (32) + #elif (ULONG_MAX == 0xFFFFFFFFFFFFFFFF) + #define UNITY_LONG_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_LONG_WIDTH (32) + #endif /* ULONG_MAX */ +#endif + +/* Determine the size of a pointer, if not already specified. */ +#ifndef UNITY_POINTER_WIDTH + #ifdef UINTPTR_MAX + #if (UINTPTR_MAX <= 0xFFFF) + #define UNITY_POINTER_WIDTH (16) + #elif (UINTPTR_MAX <= 0xFFFFFFFF) + #define UNITY_POINTER_WIDTH (32) + #elif (UINTPTR_MAX <= 0xFFFFFFFFFFFFFFFF) + #define UNITY_POINTER_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_POINTER_WIDTH UNITY_LONG_WIDTH + #endif /* UINTPTR_MAX */ +#endif + +/*------------------------------------------------------- + * Int Support (Define types based on detected sizes) + *-------------------------------------------------------*/ + +#if (UNITY_INT_WIDTH == 32) + typedef unsigned char UNITY_UINT8; + typedef unsigned short UNITY_UINT16; + typedef unsigned int UNITY_UINT32; + typedef signed char UNITY_INT8; + typedef signed short UNITY_INT16; + typedef signed int UNITY_INT32; +#elif (UNITY_INT_WIDTH == 16) + typedef unsigned char UNITY_UINT8; + typedef unsigned int UNITY_UINT16; + typedef unsigned long UNITY_UINT32; + typedef signed char UNITY_INT8; + typedef signed int UNITY_INT16; + typedef signed long UNITY_INT32; +#else + #error Invalid UNITY_INT_WIDTH specified! (16 or 32 are supported) +#endif + +/*------------------------------------------------------- + * 64-bit Support + *-------------------------------------------------------*/ + +/* Auto-detect 64 Bit Support */ +#ifndef UNITY_SUPPORT_64 + #if UNITY_LONG_WIDTH == 64 || UNITY_POINTER_WIDTH == 64 + #define UNITY_SUPPORT_64 + #endif +#endif + +/* 64-Bit Support Dependent Configuration */ +#ifndef UNITY_SUPPORT_64 + /* No 64-bit Support */ + typedef UNITY_UINT32 UNITY_UINT; + typedef UNITY_INT32 UNITY_INT; + #define UNITY_MAX_NIBBLES (8) /* Maximum number of nibbles in a UNITY_(U)INT */ +#else + /* 64-bit Support */ + #if (UNITY_LONG_WIDTH == 32) + typedef unsigned long long UNITY_UINT64; + typedef signed long long UNITY_INT64; + #elif (UNITY_LONG_WIDTH == 64) + typedef unsigned long UNITY_UINT64; + typedef signed long UNITY_INT64; + #else + #error Invalid UNITY_LONG_WIDTH specified! (32 or 64 are supported) + #endif + typedef UNITY_UINT64 UNITY_UINT; + typedef UNITY_INT64 UNITY_INT; + #define UNITY_MAX_NIBBLES (16) /* Maximum number of nibbles in a UNITY_(U)INT */ +#endif + +/*------------------------------------------------------- + * Pointer Support + *-------------------------------------------------------*/ + +#if (UNITY_POINTER_WIDTH == 32) + #define UNITY_PTR_TO_INT UNITY_INT32 + #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX32 +#elif (UNITY_POINTER_WIDTH == 64) + #define UNITY_PTR_TO_INT UNITY_INT64 + #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX64 +#elif (UNITY_POINTER_WIDTH == 16) + #define UNITY_PTR_TO_INT UNITY_INT16 + #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX16 +#else + #error Invalid UNITY_POINTER_WIDTH specified! (16, 32 or 64 are supported) +#endif + +#ifndef UNITY_PTR_ATTRIBUTE + #define UNITY_PTR_ATTRIBUTE +#endif + +#ifndef UNITY_INTERNAL_PTR + #define UNITY_INTERNAL_PTR UNITY_PTR_ATTRIBUTE const void* +#endif + +/* optionally define UNITY_COMPARE_PTRS_ON_ZERO_ARRAY */ + +/*------------------------------------------------------- + * Float Support + *-------------------------------------------------------*/ + +#ifdef UNITY_EXCLUDE_FLOAT + +/* No Floating Point Support */ +#ifndef UNITY_EXCLUDE_DOUBLE +#define UNITY_EXCLUDE_DOUBLE /* Remove double when excluding float support */ +#endif +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +#define UNITY_EXCLUDE_FLOAT_PRINT +#endif + +#else + +/* Floating Point Support */ +#ifndef UNITY_FLOAT_PRECISION +#define UNITY_FLOAT_PRECISION (0.00001f) +#endif +#ifndef UNITY_FLOAT_TYPE +#define UNITY_FLOAT_TYPE float +#endif +typedef UNITY_FLOAT_TYPE UNITY_FLOAT; + +/* isnan macro should be provided by math.h. Override if not macro */ +#ifndef UNITY_IS_NAN +#ifndef isnan +/* NaN is the only floating point value that does NOT equal itself. + * Therefore if n != n, then it is NaN. */ +#define UNITY_IS_NAN(n) ((n != n) ? 1 : 0) +#else +#define UNITY_IS_NAN(n) isnan(n) +#endif +#endif + +/* isinf macro should be provided by math.h. Override if not macro */ +#ifndef UNITY_IS_INF +#ifndef isinf +/* The value of Inf - Inf is NaN */ +#define UNITY_IS_INF(n) (UNITY_IS_NAN((n) - (n)) && !UNITY_IS_NAN(n)) +#else +#define UNITY_IS_INF(n) isinf(n) +#endif +#endif + +#endif + +/*------------------------------------------------------- + * Double Float Support + *-------------------------------------------------------*/ + +/* unlike float, we DON'T include by default */ +#if defined(UNITY_EXCLUDE_DOUBLE) || !defined(UNITY_INCLUDE_DOUBLE) + + /* No Floating Point Support */ + #ifndef UNITY_EXCLUDE_DOUBLE + #define UNITY_EXCLUDE_DOUBLE + #else + #undef UNITY_INCLUDE_DOUBLE + #endif + + #ifndef UNITY_EXCLUDE_FLOAT + #ifndef UNITY_DOUBLE_TYPE + #define UNITY_DOUBLE_TYPE double + #endif + typedef UNITY_FLOAT UNITY_DOUBLE; + /* For parameter in UnityPrintFloat(UNITY_DOUBLE), which aliases to double or float */ + #endif + +#else + + /* Double Floating Point Support */ + #ifndef UNITY_DOUBLE_PRECISION + #define UNITY_DOUBLE_PRECISION (1e-12) + #endif + + #ifndef UNITY_DOUBLE_TYPE + #define UNITY_DOUBLE_TYPE double + #endif + typedef UNITY_DOUBLE_TYPE UNITY_DOUBLE; + +#endif + +/*------------------------------------------------------- + * Output Method: stdout (DEFAULT) + *-------------------------------------------------------*/ +#ifndef UNITY_OUTPUT_CHAR + /* Default to using putchar, which is defined in stdio.h */ + #include + #define UNITY_OUTPUT_CHAR(a) (void)putchar(a) +#else + /* If defined as something else, make sure we declare it here so it's ready for use */ + #ifdef UNITY_OUTPUT_CHAR_HEADER_DECLARATION + extern void UNITY_OUTPUT_CHAR_HEADER_DECLARATION; + #endif +#endif + +#ifndef UNITY_OUTPUT_FLUSH + #ifdef UNITY_USE_FLUSH_STDOUT + /* We want to use the stdout flush utility */ + #include + #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout) + #else + /* We've specified nothing, therefore flush should just be ignored */ + #define UNITY_OUTPUT_FLUSH() (void)0 + #endif +#else + /* If defined as something else, make sure we declare it here so it's ready for use */ + #ifdef UNITY_OUTPUT_FLUSH_HEADER_DECLARATION + extern void UNITY_OUTPUT_FLUSH_HEADER_DECLARATION; + #endif +#endif + +#ifndef UNITY_OUTPUT_FLUSH +#define UNITY_FLUSH_CALL() +#else +#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH() +#endif + +#ifndef UNITY_PRINT_EOL +#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') +#endif + +#ifndef UNITY_OUTPUT_START +#define UNITY_OUTPUT_START() +#endif + +#ifndef UNITY_OUTPUT_COMPLETE +#define UNITY_OUTPUT_COMPLETE() +#endif + +#ifdef UNITY_INCLUDE_EXEC_TIME + #if !defined(UNITY_EXEC_TIME_START) && \ + !defined(UNITY_EXEC_TIME_STOP) && \ + !defined(UNITY_PRINT_EXEC_TIME) && \ + !defined(UNITY_TIME_TYPE) + /* If none any of these macros are defined then try to provide a default implementation */ + + #if defined(UNITY_CLOCK_MS) + /* This is a simple way to get a default implementation on platforms that support getting a millisecond counter */ + #define UNITY_TIME_TYPE UNITY_UINT + #define UNITY_EXEC_TIME_START() Unity.CurrentTestStartTime = UNITY_CLOCK_MS() + #define UNITY_EXEC_TIME_STOP() Unity.CurrentTestStopTime = UNITY_CLOCK_MS() + #define UNITY_PRINT_EXEC_TIME() { \ + UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \ + UnityPrint(" ("); \ + UnityPrintNumberUnsigned(execTimeMs); \ + UnityPrint(" ms)"); \ + } + #elif defined(_WIN32) + #include + #define UNITY_TIME_TYPE clock_t + #define UNITY_GET_TIME(t) t = (clock_t)((clock() * 1000) / CLOCKS_PER_SEC) + #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime) + #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime) + #define UNITY_PRINT_EXEC_TIME() { \ + UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \ + UnityPrint(" ("); \ + UnityPrintNumberUnsigned(execTimeMs); \ + UnityPrint(" ms)"); \ + } + #elif defined(__unix__) || defined(__APPLE__) + #include + #define UNITY_TIME_TYPE struct timespec + #define UNITY_GET_TIME(t) clock_gettime(CLOCK_MONOTONIC, &t) + #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime) + #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime) + #define UNITY_PRINT_EXEC_TIME() { \ + UNITY_UINT execTimeMs = ((Unity.CurrentTestStopTime.tv_sec - Unity.CurrentTestStartTime.tv_sec) * 1000L); \ + execTimeMs += ((Unity.CurrentTestStopTime.tv_nsec - Unity.CurrentTestStartTime.tv_nsec) / 1000000L); \ + UnityPrint(" ("); \ + UnityPrintNumberUnsigned(execTimeMs); \ + UnityPrint(" ms)"); \ + } + #endif + #endif +#endif + +#ifndef UNITY_EXEC_TIME_START +#define UNITY_EXEC_TIME_START() do { /* nothing*/ } while (0) +#endif + +#ifndef UNITY_EXEC_TIME_STOP +#define UNITY_EXEC_TIME_STOP() do { /* nothing*/ } while (0) +#endif + +#ifndef UNITY_TIME_TYPE +#define UNITY_TIME_TYPE UNITY_UINT +#endif + +#ifndef UNITY_PRINT_EXEC_TIME +#define UNITY_PRINT_EXEC_TIME() do { /* nothing*/ } while (0) +#endif + +/*------------------------------------------------------- + * Footprint + *-------------------------------------------------------*/ + +#ifndef UNITY_LINE_TYPE +#define UNITY_LINE_TYPE UNITY_UINT +#endif + +#ifndef UNITY_COUNTER_TYPE +#define UNITY_COUNTER_TYPE UNITY_UINT +#endif + +/*------------------------------------------------------- + * Internal Structs Needed + *-------------------------------------------------------*/ + +typedef void (*UnityTestFunction)(void); + +#define UNITY_DISPLAY_RANGE_INT (0x10) +#define UNITY_DISPLAY_RANGE_UINT (0x20) +#define UNITY_DISPLAY_RANGE_HEX (0x40) +#define UNITY_DISPLAY_RANGE_CHAR (0x80) + +typedef enum +{ + UNITY_DISPLAY_STYLE_INT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT8 = 1 + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT16 = 2 + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT32 = 4 + UNITY_DISPLAY_RANGE_INT, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_INT64 = 8 + UNITY_DISPLAY_RANGE_INT, +#endif + + UNITY_DISPLAY_STYLE_UINT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT8 = 1 + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT16 = 2 + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT32 = 4 + UNITY_DISPLAY_RANGE_UINT, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_UINT64 = 8 + UNITY_DISPLAY_RANGE_UINT, +#endif + + UNITY_DISPLAY_STYLE_HEX8 = 1 + UNITY_DISPLAY_RANGE_HEX, + UNITY_DISPLAY_STYLE_HEX16 = 2 + UNITY_DISPLAY_RANGE_HEX, + UNITY_DISPLAY_STYLE_HEX32 = 4 + UNITY_DISPLAY_RANGE_HEX, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_HEX64 = 8 + UNITY_DISPLAY_RANGE_HEX, +#endif + + UNITY_DISPLAY_STYLE_CHAR = 1 + UNITY_DISPLAY_RANGE_CHAR + UNITY_DISPLAY_RANGE_INT, + + UNITY_DISPLAY_STYLE_UNKNOWN +} UNITY_DISPLAY_STYLE_T; + +typedef enum +{ + UNITY_WITHIN = 0x0, + UNITY_EQUAL_TO = 0x1, + UNITY_GREATER_THAN = 0x2, + UNITY_GREATER_OR_EQUAL = 0x2 + UNITY_EQUAL_TO, + UNITY_SMALLER_THAN = 0x4, + UNITY_SMALLER_OR_EQUAL = 0x4 + UNITY_EQUAL_TO, + UNITY_NOT_EQUAL = 0x0, + UNITY_UNKNOWN +} UNITY_COMPARISON_T; + +#ifndef UNITY_EXCLUDE_FLOAT +typedef enum UNITY_FLOAT_TRAIT +{ + UNITY_FLOAT_IS_NOT_INF = 0, + UNITY_FLOAT_IS_INF, + UNITY_FLOAT_IS_NOT_NEG_INF, + UNITY_FLOAT_IS_NEG_INF, + UNITY_FLOAT_IS_NOT_NAN, + UNITY_FLOAT_IS_NAN, + UNITY_FLOAT_IS_NOT_DET, + UNITY_FLOAT_IS_DET, + UNITY_FLOAT_INVALID_TRAIT +} UNITY_FLOAT_TRAIT_T; +#endif + +typedef enum +{ + UNITY_ARRAY_TO_VAL = 0, + UNITY_ARRAY_TO_ARRAY, + UNITY_ARRAY_UNKNOWN +} UNITY_FLAGS_T; + +struct UNITY_STORAGE_T +{ + const char* TestFile; + const char* CurrentTestName; +#ifndef UNITY_EXCLUDE_DETAILS + const char* CurrentDetail1; + const char* CurrentDetail2; +#endif + UNITY_LINE_TYPE CurrentTestLineNumber; + UNITY_COUNTER_TYPE NumberOfTests; + UNITY_COUNTER_TYPE TestFailures; + UNITY_COUNTER_TYPE TestIgnores; + UNITY_COUNTER_TYPE CurrentTestFailed; + UNITY_COUNTER_TYPE CurrentTestIgnored; +#ifdef UNITY_INCLUDE_EXEC_TIME + UNITY_TIME_TYPE CurrentTestStartTime; + UNITY_TIME_TYPE CurrentTestStopTime; +#endif +#ifndef UNITY_EXCLUDE_SETJMP_H + jmp_buf AbortFrame; +#endif +}; + +extern struct UNITY_STORAGE_T Unity; + +/*------------------------------------------------------- + * Test Suite Management + *-------------------------------------------------------*/ + +void UnityBegin(const char* filename); +int UnityEnd(void); +void UnitySetTestFile(const char* filename); +void UnityConcludeTest(void); + +#ifndef RUN_TEST +void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum); +#else +#define UNITY_SKIP_DEFAULT_RUNNER +#endif + +/*------------------------------------------------------- + * Details Support + *-------------------------------------------------------*/ + +#ifdef UNITY_EXCLUDE_DETAILS +#define UNITY_CLR_DETAILS() +#define UNITY_SET_DETAIL(d1) +#define UNITY_SET_DETAILS(d1,d2) +#else +#define UNITY_CLR_DETAILS() do { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } while (0) +#define UNITY_SET_DETAIL(d1) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } while (0) +#define UNITY_SET_DETAILS(d1,d2) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } while (0) + +#ifndef UNITY_DETAIL1_NAME +#define UNITY_DETAIL1_NAME "Function" +#endif + +#ifndef UNITY_DETAIL2_NAME +#define UNITY_DETAIL2_NAME "Argument" +#endif +#endif + +#ifdef UNITY_PRINT_TEST_CONTEXT +void UNITY_PRINT_TEST_CONTEXT(void); +#endif + +/*------------------------------------------------------- + * Test Output + *-------------------------------------------------------*/ + +void UnityPrint(const char* string); + +#ifdef UNITY_INCLUDE_PRINT_FORMATTED +void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...); +#endif + +void UnityPrintLen(const char* string, const UNITY_UINT32 length); +void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number); +void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style); +void UnityPrintNumber(const UNITY_INT number_to_print); +void UnityPrintNumberUnsigned(const UNITY_UINT number); +void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print); + +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +void UnityPrintFloat(const UNITY_DOUBLE input_number); +#endif + +/*------------------------------------------------------- + * Test Assertion Functions + *------------------------------------------------------- + * Use the macros below this section instead of calling + * these directly. The macros have a consistent naming + * convention and will pull in file and line information + * for you. */ + +void UnityAssertEqualNumber(const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, + const UNITY_INT actual, + const UNITY_COMPARISON_T compare, + const char *msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags); + +void UnityAssertBits(const UNITY_INT mask, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualString(const char* expected, + const char* actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualStringLen(const char* expected, + const char* actual, + const UNITY_UINT32 length, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualStringArray( UNITY_INTERNAL_PTR expected, + const char** actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertEqualMemory( UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 length, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertNumbersWithin(const UNITY_UINT delta, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, + UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags); + +#ifndef UNITY_EXCLUDE_SETJMP_H +UNITY_NORETURN void UnityFail(const char* message, const UNITY_LINE_TYPE line); +UNITY_NORETURN void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); +#else +void UnityFail(const char* message, const UNITY_LINE_TYPE line); +void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); +#endif + +void UnityMessage(const char* message, const UNITY_LINE_TYPE line); + +#ifndef UNITY_EXCLUDE_FLOAT +void UnityAssertFloatsWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertFloatsNotWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertGreaterOrLessFloat(const UNITY_FLOAT threshold, + const UNITY_FLOAT actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE linenumber); + +void UnityAssertWithinFloatArray(const UNITY_FLOAT delta, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertFloatSpecial(const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style); +#endif + +#ifndef UNITY_EXCLUDE_DOUBLE +void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertDoublesNotWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertGreaterOrLessDouble(const UNITY_DOUBLE threshold, + const UNITY_DOUBLE actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE linenumber); + +void UnityAssertWithinDoubleArray(const UNITY_DOUBLE delta, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style); +#endif + +/*------------------------------------------------------- + * Helpers + *-------------------------------------------------------*/ + +UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size); +#ifndef UNITY_EXCLUDE_FLOAT +UNITY_INTERNAL_PTR UnityFloatToPtr(const float num); +#endif +#ifndef UNITY_EXCLUDE_DOUBLE +UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num); +#endif + +/*------------------------------------------------------- + * Error Strings We Might Need + *-------------------------------------------------------*/ + +extern const char UnityStrOk[]; +extern const char UnityStrPass[]; +extern const char UnityStrFail[]; +extern const char UnityStrIgnore[]; + +extern const char UnityStrErrFloat[]; +extern const char UnityStrErrDouble[]; +extern const char UnityStrErr64[]; +extern const char UnityStrErrShorthand[]; + +/*------------------------------------------------------- + * Test Running Macros + *-------------------------------------------------------*/ + +#ifdef UNITY_TEST_PROTECT +#define TEST_PROTECT() UNITY_TEST_PROTECT() +#else +#ifndef UNITY_EXCLUDE_SETJMP_H +#define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0) +#else +#define TEST_PROTECT() 1 +#endif +#endif + +#ifdef UNITY_TEST_ABORT +#define TEST_ABORT() UNITY_TEST_ABORT() +#else +#ifndef UNITY_EXCLUDE_SETJMP_H +#define TEST_ABORT() longjmp(Unity.AbortFrame, 1) +#else +#define TEST_ABORT() return +#endif +#endif + +/* Automatically enable variadic macros support, if it not enabled before */ +#ifndef UNITY_SUPPORT_VARIADIC_MACROS + #ifdef __STDC_VERSION__ + #if __STDC_VERSION__ >= 199901L + #define UNITY_SUPPORT_VARIADIC_MACROS + #endif + #endif +#endif + +/* This tricky series of macros gives us an optional line argument to treat it as RUN_TEST(func, num=__LINE__) */ +#ifndef RUN_TEST +#ifdef UNITY_SUPPORT_VARIADIC_MACROS +#define RUN_TEST(...) RUN_TEST_AT_LINE(__VA_ARGS__, __LINE__, throwaway) +#define RUN_TEST_AT_LINE(func, line, ...) UnityDefaultTestRun(func, #func, line) +#endif +#endif + +/* Enable default macros for masking param tests test cases */ +#ifdef UNITY_SUPPORT_TEST_CASES + #ifdef UNITY_SUPPORT_VARIADIC_MACROS + #if !defined(TEST_CASE) && !defined(UNITY_EXCLUDE_TEST_CASE) + #define TEST_CASE(...) + #endif + #if !defined(TEST_RANGE) && !defined(UNITY_EXCLUDE_TEST_RANGE) + #define TEST_RANGE(...) + #endif + #if !defined(TEST_MATRIX) && !defined(UNITY_EXCLUDE_TEST_MATRIX) + #define TEST_MATRIX(...) + #endif + #endif +#endif + +/* If we can't do the tricky version, we'll just have to require them to always include the line number */ +#ifndef RUN_TEST +#ifdef CMOCK +#define RUN_TEST(func, num) UnityDefaultTestRun(func, #func, num) +#else +#define RUN_TEST(func) UnityDefaultTestRun(func, #func, __LINE__) +#endif +#endif + +#define TEST_LINE_NUM (Unity.CurrentTestLineNumber) +#define TEST_IS_IGNORED (Unity.CurrentTestIgnored) +#define UNITY_NEW_TEST(a) \ + Unity.CurrentTestName = (a); \ + Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)(__LINE__); \ + Unity.NumberOfTests++; + +#ifndef UNITY_BEGIN +#define UNITY_BEGIN() UnityBegin(__FILE__) +#endif + +#ifndef UNITY_END +#define UNITY_END() UnityEnd() +#endif + +#ifndef UNITY_SHORTHAND_AS_INT +#ifndef UNITY_SHORTHAND_AS_MEM +#ifndef UNITY_SHORTHAND_AS_NONE +#ifndef UNITY_SHORTHAND_AS_RAW +#define UNITY_SHORTHAND_AS_OLD +#endif +#endif +#endif +#endif + +/*----------------------------------------------- + * Command Line Argument Support + *-----------------------------------------------*/ + +#ifdef UNITY_USE_COMMAND_LINE_ARGS +int UnityParseOptions(int argc, char** argv); +int UnityTestMatches(void); +#endif + +/*------------------------------------------------------- + * Basic Fail and Ignore + *-------------------------------------------------------*/ + +#define UNITY_TEST_FAIL(line, message) UnityFail( (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_IGNORE(line, message) UnityIgnore( (message), (UNITY_LINE_TYPE)(line)) + +/*------------------------------------------------------- + * Test Asserts + *-------------------------------------------------------*/ + +#define UNITY_TEST_ASSERT(condition, line, message) do { if (condition) { /* nothing*/ } else { UNITY_TEST_FAIL((line), (message)); } } while (0) +#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (line), (message)) +#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (line), (message)) +#define UNITY_TEST_ASSERT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) == 0), (line), (message)) +#define UNITY_TEST_ASSERT_NOT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) != 0), (line), (message)) + +#define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_EQUAL_INT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_EQUAL_INT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_EQUAL_INT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_EQUAL_UINT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_EQUAL_UINT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_EQUAL_UINT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_EQUAL_UINT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_EQUAL_HEX8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_EQUAL_HEX16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_EQUAL_HEX32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_EQUAL_CHAR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) +#define UNITY_TEST_ASSERT_BITS(mask, expected, actual, line, message) UnityAssertBits((UNITY_INT)(mask), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line)) + +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_GREATER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16) (threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32) (threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_INT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_INT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_INT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_INT16) (expected), (UNITY_INT)(UNITY_INT16) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_INT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_INT32) (expected), (UNITY_INT)(UNITY_INT32) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_UINT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_UINT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_UINT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_UINT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_HEX8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_HEX16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_HEX32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_CHAR_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY) + + +#define UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_PTR_TO_INT)(expected), (UNITY_PTR_TO_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER) +#define UNITY_TEST_ASSERT_EQUAL_STRING(expected, actual, line, message) UnityAssertEqualString((const char*)(expected), (const char*)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len, line, message) UnityAssertEqualStringLen((const char*)(expected), (const char*)(actual), (UNITY_UINT32)(len), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_MEMORY(expected, actual, len, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), 1, (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) + +#define UNITY_TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY) + +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT16)(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT32)(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_PTR_TO_INT) (expected), (UNITY_POINTER_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_VAL) + +#ifdef UNITY_SUPPORT_64 +#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) +#else +#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#endif + +#ifdef UNITY_EXCLUDE_FLOAT +#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#else +#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsNotWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)(delta), (const UNITY_FLOAT*)(expected), (const UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)0, (const UNITY_FLOAT*)(expected), (const UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)0, UnityFloatToPtr(expected), (const UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) +#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) +#endif + +#ifdef UNITY_EXCLUDE_DOUBLE +#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#else +#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesNotWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)(delta), (const UNITY_DOUBLE*)(expected), (const UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)0, (const UNITY_DOUBLE*)(expected), (const UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)0, UnityDoubleToPtr(expected), (const UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) +#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) +#endif + +/* End of UNITY_INTERNALS_H */ +#endif diff --git a/src/worker/test/xce_sched_test.c b/src/worker/test/xce_sched_test.c new file mode 100644 index 00000000..6e8251e8 --- /dev/null +++ b/src/worker/test/xce_sched_test.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 1024 + +static volatile int running = 1; +static int server_fd = -1; + +static void signal_handler(int signo) { + if (signo == SIGINT || signo == SIGTERM) { + printf("\nReceived signal %d, shutting down...\n", signo); + running = 0; + if (server_fd != -1) { + close(server_fd); + } + } +} + +// 获取当前时间戳字符串 +static void get_timestamp(char* timestamp, size_t size) { + time_t now; + struct tm* timeinfo; + + time(&now); + timeinfo = localtime(&now); + strftime(timestamp, size, "%Y-%m-%d %H:%M:%S", timeinfo); +} + +// 从JSON字符串中解析CPU核心数 +static int parse_cpu_cores(const char* json) { + int cores = 0; + if (sscanf(json, "{\"slots\":%d}", &cores) == 1) { + return cores; + } + return -1; +} + +int main() { + int client_fd; + struct sockaddr_in address; + int opt = 1; + socklen_t addrlen = sizeof(address); + char buffer[BUFFER_SIZE] = {0}; + char timestamp[64]; + const char* tasks[] = {"sleep 7", "sleep 5", "sleep 6", "date"}; + + // 设置随机数种子 + srand(time(NULL)); + + // 设置信号处理 + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + // 创建服务器套接字 + server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd == 0) { + perror("socket failed"); + exit(EXIT_FAILURE); + } + + // 设置套接字选项 + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { + perror("setsockopt failed"); + close(server_fd); + exit(EXIT_FAILURE); + } + + // 绑定套接字 + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(6789); + + if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) { + perror("bind failed"); + close(server_fd); + exit(EXIT_FAILURE); + } + + // 监听连接 + if (listen(server_fd, 3) < 0) { + perror("listen failed"); + close(server_fd); + exit(EXIT_FAILURE); + } + + printf("Server listening on port 6789\n"); + + while (running) { + client_fd = accept(server_fd, (struct sockaddr*)&address, &addrlen); + if (client_fd < 0) { + if (running) { + perror("accept failed"); + } + continue; + } + + // 处理客户端请求 + int bytes_read = read(client_fd, buffer, BUFFER_SIZE); + if (bytes_read > 0) { + buffer[bytes_read] = '\0'; + get_timestamp(timestamp, sizeof(timestamp)); + printf("[%s] Received: %s\n", timestamp, buffer); + + // 解析CPU核心数 + int cpu_cores = parse_cpu_cores(buffer); + if (cpu_cores > 0) { + printf("Parsed CPU cores: %d\n", cpu_cores); + + // 随机选择一个任务 + const char* task = tasks[rand() % 4]; + printf("Sending task: %s\n", task); + + // 发送任务到远端 + if (send(client_fd, task, strlen(task), 0) < 0) { + perror("send failed"); + } + } else if (cpu_cores == 0) { + printf("The server is running at full capacity\n"); + } else { + printf("Failed to parse CPU cores\n"); + } + } + + close(client_fd); + } + + close(server_fd); + printf("Server shut down\n"); + return 0; +} \ No newline at end of file diff --git a/src/worker/test/xcecd_daemon_test.c b/src/worker/test/xcecd_daemon_test.c new file mode 100644 index 00000000..81a96e0d --- /dev/null +++ b/src/worker/test/xcecd_daemon_test.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include "unity.h" +#include "../include/xcecd.h" +#include "../include/thread_pool.h" +#include "../include/system_monitor.h" +#include "../include/process_manager.h" +#include "../include/connection_handler.h" + +// Mock function declarations +static int mock_network_start_called = 0; +static int mock_thread_pool_create_called = 0; +static bool mock_network_start_return = true; + +void setUp(void) { + mock_network_start_called = 0; + mock_thread_pool_create_called = 0; + mock_network_start_return = true; +} + +void tearDown(void) { + xcecd_cleanup(); +} + +// Test successful xcecd initialization +void test_xcecd_init_success(void) { + xcecd_config_t config = { + .thread_pool_size = 4, + .resource_update_interval = 5, + .job_status_update_interval = 1, + .port = 8080, + .acct_file_path = "/tmp/xcecd.acct", + .log_file_path = "/tmp/xcecd.log", + .domain_sock_path = "/tmp/xcecd.sock", + .remote_host = "localhost", + .remote_port = 6789, + .update_interval = 2 + }; + int result = xcecd_init(&config); + TEST_ASSERT_EQUAL_INT(0, result); +} + +// Test xcecd initialization with null config +void test_xcecd_init_null_config(void) { + int result = xcecd_init(NULL); + TEST_ASSERT_EQUAL_INT(-1, result); +} + +// Test xcecd initialization with invalid parameters +void test_xcecd_init_invalid_params(void) { + xcecd_config_t config = { + .thread_pool_size = 0, // Invalid size + .resource_update_interval = 0, + .job_status_update_interval = 0, + .port = 0, + .acct_file_path = NULL, + .log_file_path = NULL, + .domain_sock_path = NULL, + .remote_host = NULL, + .remote_port = 0, + .update_interval = 0 + }; + int result = xcecd_init(&config); + TEST_ASSERT_EQUAL_INT(-1, result); +} + +// Test xcecd start without initialization +void test_xcecd_start_without_init(void) { + int result = xcecd_start(); + TEST_ASSERT_EQUAL_INT(-1, result); +} + +// Test xcecd stop functionality +void test_xcecd_stop(void) { + xcecd_config_t config = { + .thread_pool_size = 4, + .resource_update_interval = 5, + .job_status_update_interval = 1, + .port = 8080, + .acct_file_path = "/tmp/xcecd.acct", + .log_file_path = "/tmp/xcecd.log", + .domain_sock_path = "/tmp/xcecd.sock", + .remote_host = "localhost", + .remote_port = 6789, + .update_interval = 2 + }; + xcecd_init(&config); + xcecd_stop(); + // Verify xcecd is stopped by trying to start it + int result = xcecd_start(); + TEST_ASSERT_EQUAL_INT(-1, result); +} + +// Test cleanup functionality +void test_xcecd_cleanup(void) { + xcecd_config_t config = { + .thread_pool_size = 4, + .resource_update_interval = 5, + .job_status_update_interval = 1, + .port = 8080, + .acct_file_path = "/tmp/xcecd.acct", + .log_file_path = "/tmp/xcecd.log", + .domain_sock_path = "/tmp/xcecd.sock", + .remote_host = "localhost", + .remote_port = 6789, + .update_interval = 2 + }; + xcecd_init(&config); + xcecd_cleanup(); + // Verify cleanup was successful by trying to start + int result = xcecd_start(); + TEST_ASSERT_EQUAL_INT(-1, result); +} + +// Test multiple init/cleanup cycles +void test_xcecd_multiple_init_cleanup(void) { + xcecd_config_t config = { + .thread_pool_size = 4, + .resource_update_interval = 5, + .job_status_update_interval = 1, + .port = 8080, + .acct_file_path = "/tmp/xcecd.acct", + .log_file_path = "/tmp/xcecd.log", + .domain_sock_path = "/tmp/xcecd.sock", + .remote_host = "localhost", + .remote_port = 6789, + .update_interval = 2 + }; + + for (int i = 0; i < 5; i++) { + int init_result = xcecd_init(&config); + TEST_ASSERT_EQUAL_INT(0, init_result); + xcecd_cleanup(); + } +} + +// Test initialization with minimum values +void test_xcecd_init_minimum_values(void) { + xcecd_config_t config = { + .thread_pool_size = 1, + .resource_update_interval = 1, + .job_status_update_interval = 1, + .port = 1024, + .acct_file_path = "/tmp/xcecd.acct", + .log_file_path = "/tmp/xcecd.log", + .domain_sock_path = "/tmp/xcecd.sock", + .remote_host = "localhost", + .remote_port = 6789, + .update_interval = 1 + }; + int result = xcecd_init(&config); + TEST_ASSERT_EQUAL_INT(0, result); +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_xcecd_init_success); + RUN_TEST(test_xcecd_init_null_config); + RUN_TEST(test_xcecd_init_invalid_params); + RUN_TEST(test_xcecd_start_without_init); + RUN_TEST(test_xcecd_stop); + RUN_TEST(test_xcecd_cleanup); + RUN_TEST(test_xcecd_multiple_init_cleanup); + RUN_TEST(test_xcecd_init_minimum_values); + + return UNITY_END(); +} \ No newline at end of file diff --git a/src/worker/test/xcecd_worker_test.c b/src/worker/test/xcecd_worker_test.c new file mode 100644 index 00000000..7242efff --- /dev/null +++ b/src/worker/test/xcecd_worker_test.c @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "../include/xcecd.h" +#include "../include/xcecd_worker.h" +#include "../include/xcecd_utils.h" +#include "../include/process_manager.h" + +#define SOCKET_PATH "/tmp/xcecd.sock" +#define BUFFER_SIZE 4096 + +static bool reported_flag = false; + +// Mock function declarations +static int mock_socket_fd = -1; +static int mock_connect_called = 0; +static int mock_send_called = 0; +static int mock_recv_called = 0; +static int mock_close_called = 0; +static int mock_select_called = 0; +static int mock_waitpid_called = 0; +static int mock_process_manager_start_process_called = 0; + +static int mock_connect_return = 0; +static int mock_send_return = 0; +static int mock_recv_return = 0; +static int mock_select_return = 0; +static int mock_waitpid_return = 0; +static int mock_process_manager_start_process_return = 0; + +void setUp(void) { + mock_socket_fd = -1; + mock_connect_called = 0; + mock_send_called = 0; + mock_recv_called = 0; + mock_close_called = 0; + mock_select_called = 0; + mock_waitpid_called = 0; + mock_process_manager_start_process_called = 0; + + mock_connect_return = 0; + mock_send_return = 0; + mock_recv_return = 0; + mock_select_return = 0; + mock_waitpid_return = 0; + mock_process_manager_start_process_return = 0; +} + +void tearDown(void) { + // 清理资源 +} + +// Mock functions +int socket(int domain, int type, int protocol) { + return mock_socket_fd; +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + mock_connect_called++; + return mock_connect_return; +} + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) { + mock_send_called++; + return mock_send_return; +} + +ssize_t recv(int sockfd, void *buf, size_t len, int flags) { + mock_recv_called++; + if (mock_recv_return > 0) { + strncpy(buf, "sleep 5", len); + } + return mock_recv_return; +} + +int close(int fd) { + mock_close_called++; + return 0; +} + +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { + mock_select_called++; + return mock_select_return; +} + +pid_t waitpid(pid_t pid, int *status, int options) { + mock_waitpid_called++; + *status = 0; + return mock_waitpid_return; +} + +__attribute__((weak)) int process_manager_start_process(process_manager_t* manager, const char* command) { + mock_process_manager_start_process_called++; + return mock_process_manager_start_process_return; +} + +static char* get_task_command(void) { + int sockfd; + struct sockaddr_un addr; + + if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket error"); + return NULL; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); + + if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + perror("connect error"); + close(sockfd); + return NULL; + } + + //int flags = fcntl(sockfd, F_GETFL, 0); + //fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(sockfd, &read_fds); + + struct timeval timeout = {1, 0}; + + int select_ret = select(sockfd + 1, &read_fds, NULL, NULL, &timeout); + if (select_ret > 0 && FD_ISSET(sockfd, &read_fds)) { + char command[BUFFER_SIZE] = {0}; + ssize_t bytes_read = read(sockfd, command, sizeof(command) - 1); + if (bytes_read > 0) { + command[bytes_read] = '\0'; + close(sockfd); + return strndup(command, sizeof(command)); + } else if (bytes_read == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { + perror("read error"); + } + } else if (select_ret == -1) { + perror("select error"); + } else { + printf("Socket not ready for reading\n"); + } + + close(sockfd); + return NULL; +} + +static void report_status_to_master(int pid, int status) { + int sockfd; + struct sockaddr_un addr; + + if (reported_flag) { + return; + } + + if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket error"); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); + + if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + perror("connect error"); + close(sockfd); + return; + } + + //int flags = fcntl(sockfd, F_GETFL, 0); + //fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + + fd_set write_fds; + FD_ZERO(&write_fds); + FD_SET(sockfd, &write_fds); + + struct timeval timeout = {1, 0}; + + int select_ret = select(sockfd + 1, NULL, &write_fds, NULL, &timeout); + if (select_ret > 0 && FD_ISSET(sockfd, &write_fds)) { + char buffer[BUFFER_SIZE] = {0}; + snprintf(buffer, sizeof(buffer), "WORKER_PID: %d JOB_PID: %d STATUS: %d", getpid(), pid, status); + ssize_t bytes_written = send(sockfd, buffer, strlen(buffer), MSG_NOSIGNAL); + if (bytes_written < 0) { + perror("send error"); + } else { + printf("Reported status to master\n"); + fflush(stdout); + reported_flag = true; + } + } else if (select_ret == -1) { + perror("select error"); + } else { + printf("Socket not ready for writing\n"); + } + + close(sockfd); +} + +static void handle_task_output(bool peek) { + if (!peek) { + return; + } + + printf("Succeed!"); + fflush(stdout); +} + +// Test cases +void test_xcecd_worker_init(void) { + // 测试初始化 + process_manager_t* manager = process_manager_create(); + TEST_ASSERT_NOT_NULL(manager); + process_manager_destroy(manager); +} + +void test_xcecd_worker_get_task_command(void) { + // 测试获取任务命令 + mock_socket_fd = 1; + mock_connect_return = 0; + mock_recv_return = 8; + mock_select_return = 1; + + char* command = get_task_command(); + TEST_ASSERT_NOT_NULL(command); + TEST_ASSERT_EQUAL_STRING("sleep 5", command); + free(command); + + TEST_ASSERT_EQUAL_INT(1, mock_connect_called); + TEST_ASSERT_EQUAL_INT(1, mock_recv_called); + TEST_ASSERT_EQUAL_INT(1, mock_select_called); + TEST_ASSERT_EQUAL_INT(1, mock_close_called); +} + +void test_xcecd_worker_handle_task_output(void) { + // 测试处理任务输出 + process_manager_t* manager = process_manager_create(); + process_info_t proc; + proc.output_fd = 1; + manager->processes = &proc; + manager->process_count = 1; + + handle_task_output(true); + + process_manager_destroy(manager); +} + +void test_xcecd_worker_report_status_to_master(void) { + // 测试报告状态到主进程 + mock_socket_fd = 1; + mock_connect_return = 0; + mock_send_return = 20; + mock_select_return = 1; + + report_status_to_master(1234, WORKER_DONE); + + TEST_ASSERT_EQUAL_INT(1, mock_connect_called); + TEST_ASSERT_EQUAL_INT(1, mock_send_called); + TEST_ASSERT_EQUAL_INT(1, mock_select_called); + TEST_ASSERT_EQUAL_INT(1, mock_close_called); +} + +void test_xcecd_worker_main(void) { + // 测试worker主函数 + mock_socket_fd = 1; + mock_connect_return = 0; + mock_recv_return = 8; + mock_select_return = 1; + mock_process_manager_start_process_return = 0; + mock_waitpid_return = 1234; + + int result = main(); + TEST_ASSERT_EQUAL_INT(0, result); + + TEST_ASSERT_EQUAL_INT(1, mock_connect_called); + TEST_ASSERT_EQUAL_INT(1, mock_recv_called); + TEST_ASSERT_EQUAL_INT(1, mock_select_called); + TEST_ASSERT_EQUAL_INT(1, mock_close_called); + TEST_ASSERT_EQUAL_INT(1, mock_process_manager_start_process_called); + TEST_ASSERT_EQUAL_INT(1, mock_waitpid_called); +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_xcecd_worker_init); + RUN_TEST(test_xcecd_worker_get_task_command); + RUN_TEST(test_xcecd_worker_handle_task_output); + RUN_TEST(test_xcecd_worker_report_status_to_master); + RUN_TEST(test_xcecd_worker_main); + + return UNITY_END(); +} \ No newline at end of file