├── .clang-format ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── include ├── common.h ├── config.h ├── connection.h ├── eviction_policy.h ├── hashtable.h ├── log_level.h ├── logger.h ├── lru_manager.h ├── mock_malloc.h ├── response.h ├── server.h ├── signal_handler.h └── ttl_manager.h ├── run_tests.sh ├── src ├── connection.c ├── hashtable.c ├── logger.c ├── lru_manager.c ├── main.c ├── server.c ├── signal_handler.c └── ttl_manager.c ├── test ├── mock_malloc.c ├── test_app.c ├── test_common_cache.c ├── test_common_cache.h ├── test_lru_cache.c ├── test_lru_cache.h ├── test_ttl_cache.c ├── test_ttl_cache.h ├── util.c └── util.h └── unity ├── unity.c ├── unity.h └── unity_internals.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | *.log 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Stylianos Voukatas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc -std=gnu11 2 | # Flags to check for race conditions 3 | # You might need this in order to run properly 4 | # sudo sysctl vm.mmap_rnd_bits=28 5 | #CFLAGS = -Iunity -Iinclude -Wall -Wextra -Werror -g -pthread -fsanitize=thread -O0 6 | CFLAGS = -Iunity -Iinclude -Wall -Wextra -Werror -g -pthread -fprofile-arcs -ftest-coverage -O0 7 | OPTIMIZED_FLAGS = -Iinclude -Wall -Wextra -Werror -O2 -s -pthread -DNDEBUG 8 | 9 | APP_SRC = $(wildcard src/*.c) 10 | TEST_SRC = $(wildcard test/*.c) $(wildcard unity/*.c) 11 | 12 | APP_OBJ = $(APP_SRC:.c=.o) 13 | TEST_APP_SRC = $(filter-out src/main.c, $(APP_SRC)) 14 | TEST_OBJ = $(TEST_SRC:.c=.o) $(patsubst src/%.c,$(BUILD_TARGET)/%.o,$(TEST_APP_SRC)) 15 | 16 | OPTIMIZED_OBJ = $(APP_SRC:.c=.optimized.o) 17 | 18 | APP_TARGET = ccache_dbg 19 | TEST_TARGET = test_app 20 | OPTIMIZED_TARGET = ccache_prod 21 | 22 | BUILD_TARGET = test_obj 23 | 24 | ifndef EVICTION_POLICY 25 | EVICTION_POLICY = TTL 26 | endif 27 | 28 | CPPFLAGS += -DEVICTION_FLAG_$(EVICTION_POLICY) 29 | 30 | all: $(APP_TARGET) $(TEST_TARGET) $(OPTIMIZED_TARGET) 31 | @echo "CPPFLAGS = $(CPPFLAGS)" 32 | 33 | # Create the application binary 34 | $(APP_TARGET): $(APP_OBJ) 35 | $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ 36 | 37 | # Create the test binary 38 | $(TEST_TARGET): $(TEST_OBJ) 39 | $(CC) $(CFLAGS) $(CPPFLAGS) -DTESTING -o $@ $^ 40 | 41 | # Rule to create the optimized production binary 42 | $(OPTIMIZED_TARGET): $(OPTIMIZED_OBJ) 43 | $(CC) $(OPTIMIZED_FLAGS) $(CPPFLAGS) -o $@ $^ 44 | 45 | # General rule 46 | %.o: %.c 47 | $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ 48 | 49 | %.optimized.o: %.c 50 | $(CC) $(OPTIMIZED_FLAGS) $(CPPFLAGS) -c $< -o $@ 51 | 52 | # Compile test object files with TESTING defined 53 | $(BUILD_TARGET)/%.o: src/%.c 54 | mkdir -p $(BUILD_TARGET) 55 | $(CC) $(CFLAGS) $(CPPFLAGS) -DTESTING -c $< -o $@ 56 | 57 | clean: 58 | rm -f $(APP_OBJ) $(TEST_OBJ) $(APP_TARGET) $(TEST_TARGET) $(OPTIMIZED_TARGET) $(OPTIMIZED_OBJ) 59 | rm -rf $(BUILD_TARGET)/ 60 | rm -rf test/coverage.info test/out/ test_app.c.gcov 61 | rm test/mock_malloc.gcda test/mock_malloc.gcno test/test_app.gcda test/test_app.gcno unity/unity.gcda unity/unity.gcno 62 | rm -rf main.c.gcov server.c.gcov src/connection.gcda src/connection.gcno src/coverage.info src/hashtable.gcda src/hashtable.gcno src/main.gcda src/main.gcno src/out/ src/server.gcda src/server.gcno src/signal_handler.gcda src/signal_handler.gcno src/logger.gcno src/logger.gcda src/ttl_manager.gcno src/ttl_manager.gcda test/test_ttl_cache.gcno test/test_ttl_cache.gcda test/test_lru_cache.gcno test/test_lru_cache.gcda test/util.gcno test/util.gcda src/lru_manager.gcno src/lru_manager.gcda test/test_common_cache.gcda test/test_common_cache.gcno 63 | 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CCache 2 | 3 | A single-threaded event-driven cache 4 | 5 | ## Features 6 | - Event-driven, single-threaded 7 | - Uses a text-based protocol 8 | - For resource management there are two options, either build with a Time-To-Live (TTL) or Least Recently Used (LRU) 9 | 10 | ### Project Origin 11 | 12 | This project is a continuation and expansion of a smaller side project originally developed in a [different repository](https://github.com/voukatas/C-Playground). The initial version served as a prototype and was part of a collection of various smaller projects. For the original base code and the early development history, please visit the [original repository](https://github.com/voukatas/C-Playground). 13 | 14 | 15 | ## Configuration 16 | 17 | The cache is configured using the `config.h` file. The following are the current default configurations: 18 | 19 | - **Maximum Value Size**: Configured to support almost 64K bytes as a value. The actual size is slightly reduced to accommodate the `SET` keyword and the bytes for the key. 20 | - **Port**: The server listens on port `8080`. 21 | - **Maximum Epoll Events**: The maximum number of events that `epoll` can retrieve is set to `64`. 22 | - **Cleanup Interval**: The cleanup mechanism for expired keys kicks in every `60` seconds. 23 | - **Initial Hashtable Size**: The starting size of the internal core hashtable is set to `1000`. 24 | 25 | ## How to run 26 | ```bash 27 | # For building with an LRU cache 28 | make EVICTION_POLICY=LRU 29 | # or this for building with an TTL cache 30 | make EVICTION_POLICY=TTL 31 | # For production execute this 32 | ./ccache_prod 33 | # For debugging With debug symbols 34 | ./ccache_dbg 35 | # To check for memory leaks 36 | valgrind --leak-check=full --track-origins=yes -s ./ccache_dbg 37 | ``` 38 | 39 | ## How to run tests 40 | ```bash 41 | # check for memory issues 42 | valgrind --leak-check=full --track-origins=yes -s ./test_app 43 | 44 | # All in one 45 | # For LRU 46 | make clean && make EVICTION_POLICY=LRU && valgrind --leak-check=full --track-origins=yes -s ./test_app && valgrind --tool=helgrind ./test_app 47 | # or for TTL 48 | make clean && make EVICTION_POLICY=TTL && valgrind --leak-check=full --track-origins=yes -s ./test_app && valgrind --tool=helgrind ./test_app 49 | 50 | ``` 51 | ## Check the Code Coverage 52 | ```bash 53 | #Enable code coverage flags in Makefile 54 | make test_app 55 | ./test_app 56 | # check quickly the coverage 57 | #gcov your_file (eg. src/server.c) 58 | cd test_obj/ 59 | lcov --capture --directory . --output-file coverage.info && genhtml coverage.info --output-directory out 60 | # open out/index.html 61 | 62 | # Cleanup 63 | make clean 64 | 65 | ``` 66 | ## Manual check 67 | ```bash 68 | # 30 seconds TTL 69 | echo -ne "SET key_test value_test 30\r\n" | nc localhost 8080 70 | ``` 71 | 72 | ## API Usage 73 | CCache is a text-based protocol key-value store that supports basic operations like SET, GET, DELETE, and a few utility commands. Below is a description of the commands supported by the cache and how to use them: 74 | 75 | **NOTE: USE THE ONLY IF YOU SET IT UP IN TTL MODE, FOR LRU MODE OMIT THIS FIELD** 76 | 77 | 1. SET Command 78 | - Usage: SET 79 | - Description: Sets a key-value pair in the cache with a Time-To-Live (TTL) in seconds. If TTL is set to 0, then the key doesn't expire. 80 | - Response: OK\r\n if the operation is successful. Returns an error if memory allocation fails or the TTL is invalid. 81 | Example: 82 | ```bash 83 | SET my_key my_value 30\r\n 84 | OK\r\n 85 | ``` 86 | 2. GET Command 87 | - Usage: GET 88 | - Description: Retrieves the value for a given key if it exists and has not expired. 89 | - Response: \r\n if the key exists and is valid. Returns ERROR: KEY NOT FOUND\r\n if the key does not exist or has expired. 90 | Example: 91 | ```bash 92 | GET my_key\r\n 93 | my_value\r\n 94 | ``` 95 | 3. DELETE Command 96 | - Usage: DELETE 97 | - Description: Removes a key-value pair from the cache if it exists. 98 | - Response: OK\r\n if the key was successfully deleted. Returns ERROR: KEY NOT FOUND\r\n if the key does not exist. 99 | Example: 100 | ```bash 101 | DELETE my_key\r\n 102 | OK\r\n 103 | ``` 104 | 4. CONNECTIONS Command 105 | - Usage: CONNECTIONS 106 | - Description: Returns the number of active client connections to the cache server. 107 | - Response: \r\n, where is the count of active connections. 108 | Example: 109 | ```bash 110 | CONNECTIONS\r\n 111 | 1\r\n 112 | ``` 113 | 5. KEYS_NUM Command 114 | - Usage: KEYS_NUM 115 | - Description: Returns the number of keys currently stored in the cache. 116 | - Response: \r\n, where is the count of keys. 117 | Example: 118 | ```bash 119 | KEYS_NUM\r\n 120 | 2\r\n 121 | ``` 122 | ### Error Handling 123 | - Malformed Commands: Returns ERROR: UNKNOWN OR MALFORMED COMMAND\r\n if a command is not recognized or is improperly formatted. 124 | - Memory Allocation Failures: Returns ERROR: MEMORY ALLOC FAILURE\r\n if there is an issue allocating memory for the operation. 125 | - TTL Issues: Returns ERROR: INVALID TTL\r\n if the TTL provided for a SET command is not a positive integer. 126 | 127 | ### Example Usage 128 | Below is an example of how to interact with the cache using netcat (nc): 129 | ```bash 130 | # Set a key-value pair with a TTL of 30 seconds 131 | echo -ne "SET test_key test_value 30\r\n" | nc localhost 8080 132 | 133 | # Retrieve the value for an existing key 134 | echo -ne "GET test_key\r\n" | nc localhost 8080 135 | 136 | # Attempt to retrieve a non-existent key 137 | echo -ne "GET invalid_key\r\n" | nc localhost 8080 138 | 139 | # Delete an existing key 140 | echo -ne "DELETE test_key\r\n" | nc localhost 8080 141 | 142 | # Get the number of active connections 143 | echo -ne "CONNECTIONS\r\n" | nc localhost 8080 144 | 145 | # Get the number of keys currently in the cache 146 | echo -ne "KEYS_NUM\r\n" | nc localhost 8080 147 | ``` 148 | ## Dependencies 149 | CCache uses a custom hashtable library to manage key-value pairs efficiently. This library is developed as a separate project and can be found on GitHub on the same repo. 150 | 151 | ## To-Do 152 | - ~~Either remove the linked list that keeps track of the connections (since it adds management complexity) or implement a periodic validation of the connections~~ 153 | - Add API command to change the logging on the fly 154 | - Add support for more cache eviction policies (e.g., LRU, LFU) 155 | - Add more unit tests to increase code coverage and stability 156 | - Replace the stack allocations with heap allocations for the arrays that use the BUFFER_SIZE to support more than 64Kb key-value values. (Currently if more bytes are used it will overflow the stack) 157 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | #include "config.h" 5 | #include 6 | #include "logger.h" 7 | #include "hashtable.h" 8 | 9 | typedef enum { 10 | EVENT_SERVER, 11 | EVENT_CLIENT, 12 | EVENT_TIMER, 13 | 14 | } EventType; 15 | 16 | typedef struct { 17 | int fd; 18 | char read_buffer[BUFFER_SIZE]; 19 | int read_buffer_len; 20 | char write_buffer[BUFFER_SIZE]; 21 | int write_buffer_len; 22 | int write_buffer_pos; 23 | int close_after_write; 24 | } client_t; 25 | 26 | typedef struct { 27 | int event_type; // 0 for server, 1 for client and 2 for timer 28 | union { 29 | int fd; // Use this if server or timer 30 | client_t *client; // Use this if client 31 | } data; 32 | } node_data_t; 33 | 34 | 35 | //extern hash_table_t *hash_table_main; 36 | 37 | #endif // COMMON_H 38 | -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #include "log_level.h" 5 | #include "eviction_policy.h" 6 | 7 | // ======================= Versioning ======================= 8 | #define CCACHE_VERSION "0.1.0" 9 | 10 | // ======================= Default Cache Configuration ======================= 11 | /** 12 | * Production (Default) Configuration 13 | * Modify the values below to change the default behavior in production. 14 | */ 15 | 16 | /** 17 | * Port for the cache server. 18 | * Default: 8080 19 | */ 20 | #define PORT 8080 21 | 22 | /** 23 | * Maximum number of events epoll will grab at once. 24 | * Controls the event queue size for epoll. 25 | * Default: 64 events. 26 | */ 27 | #define MAX_EVENTS 64 28 | 29 | /** 30 | * Log level controls the verbosity of logs. 31 | * Available levels: 32 | * - LOG_LEVEL_DEBUG: Most verbose logging 33 | * - LOG_LEVEL_INFO: Informational messages 34 | * - LOG_LEVEL_WARN: Warnings that require attention 35 | * - LOG_LEVEL_ERROR: Error messages 36 | * - LOG_LEVEL_NONE: No logging 37 | * Default: LOG_LEVEL_DEBUG. 38 | */ 39 | #define LOG_LEVEL LOG_LEVEL_DEBUG 40 | 41 | /** 42 | * Buffer size in bytes for key/value storage. 43 | * Default: 64 KB (64 * 1024 bytes). 44 | * 45 | * IMPORTANT: DO NOT CHANGE THIS VALUE in production unless necessary. 46 | */ 47 | #define BUFFER_SIZE (64 * 1024) 48 | 49 | /** 50 | * Buffer size in bytes for TTL value. 51 | * Default: 15, This will covers millions of seconds 52 | * 53 | */ 54 | #define BUFFER_SIZE_TTL 15 55 | 56 | // ======================= Eviction Policy Configuration ======================= 57 | /** 58 | * Eviction policy for cache items. 59 | * Available policies: 60 | * - TTL (Time-To-Live): Items expire after a certain amount of time. 61 | * - LRU (Least Recently Used): Discards the least recently used items first. 62 | * Default: TTL. 63 | */ 64 | #ifdef EVICTION_FLAG_TTL 65 | #define EVICTION EVICTION_TTL 66 | #endif 67 | #ifdef EVICTION_FLAG_LRU 68 | #define EVICTION EVICTION_LRU 69 | #endif 70 | #ifndef EVICTION 71 | #define EVICTION EVICTION_TTL 72 | #endif 73 | 74 | /** 75 | * Cleanup interval in seconds for the TTL eviction policy. 76 | * This defines how frequently expired items are removed. 77 | * Default: 15 seconds. 78 | */ 79 | #define CLEAN_UP_TIME 15 80 | 81 | /** 82 | * Initial size of the hash table. 83 | * - For TTL policy, this is the starting size and will dynamically increase. 84 | * - For LRU policy, this is the maximum number of items the cache can hold. 85 | * If using LRU, a larger starting size like 10,000 is recommended. 86 | * Default: 1000 entries. 87 | */ 88 | #define HASH_TABLE_STARTING_SIZE 10000 89 | 90 | /* 91 | * The maximum capacity an LRU can store 92 | * */ 93 | #define LRU_CAPACITY 10000 94 | 95 | // ======================= Testing Configuration ======================= 96 | #ifdef TESTING 97 | /** 98 | * Testing configuration overrides. 99 | * The following values overwrite the production defaults for testing purposes. 100 | */ 101 | 102 | /** 103 | * Buffer size in bytes for key storage during testing. 104 | * Testing default: 41 bytes. 105 | */ 106 | #undef BUFFER_SIZE 107 | #define BUFFER_SIZE 41 108 | 109 | /** 110 | * Cleanup interval in seconds for testing. 111 | * Testing default: 3 seconds. 112 | */ 113 | #undef CLEAN_UP_TIME 114 | #define CLEAN_UP_TIME 3 115 | 116 | #ifdef EVICTION_FLAG_TTL 117 | #undef HASH_TABLE_STARTING_SIZE 118 | #define HASH_TABLE_STARTING_SIZE 1000 119 | #endif 120 | 121 | #ifdef EVICTION_FLAG_LRU 122 | #undef HASH_TABLE_STARTING_SIZE 123 | #define HASH_TABLE_STARTING_SIZE 5 124 | #undef LRU_CAPACITY 125 | #define LRU_CAPACITY 5 126 | #endif 127 | 128 | #undef LOG_LEVEL 129 | #define LOG_LEVEL LOG_LEVEL_DEBUG 130 | 131 | #endif 132 | 133 | #endif // CONFIG_H 134 | 135 | -------------------------------------------------------------------------------- /include/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENT_H 2 | #define CLIENT_H 3 | 4 | #include 5 | #include "config.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "common.h" 11 | #include "hashtable.h" 12 | #include "../include/ttl_manager.h" 13 | #include 14 | #include 15 | #include "../include/lru_manager.h" 16 | #include "../include/response.h" 17 | 18 | 19 | #ifdef TESTING 20 | //#warning "TESTING macro is defined" 21 | #define CONNECTIONS_TYPE atomic_int 22 | #define CONNECTIONS_INIT 0 23 | #define CONNECTIONS_INCREMENT(var) atomic_fetch_add(&(var), 1) 24 | #define CONNECTIONS_DECREMENT(var) atomic_fetch_sub(&(var), 1) 25 | #define CONNECTIONS_GET(var) atomic_load(&(var)) 26 | #define CONNECTIONS_STORE(var, val) atomic_store(&(var), (val)) 27 | #else 28 | #define CONNECTIONS_TYPE int 29 | #define CONNECTIONS_INIT 0 30 | #define CONNECTIONS_INCREMENT(var) (var++) 31 | #define CONNECTIONS_DECREMENT(var) (var--) 32 | #define CONNECTIONS_GET(var) (var) 33 | #define CONNECTIONS_STORE(var, val) (var = (val)) 34 | #endif 35 | 36 | void remove_client_from_list(node_data_t *client_data); 37 | void handle_client_read(client_t *client, struct epoll_event *ev, int epoll_fd); 38 | void handle_client_write(client_t *client, struct epoll_event *ev, int epoll_fd); 39 | void delete_resources(int epoll_fd, client_t *client, struct epoll_event *ev); 40 | //void custom_cleanup(void *arg); 41 | //bool is_entry_expired(ttl_entry_t *entry, time_t current_time); 42 | 43 | 44 | void increment_active_connections(void); 45 | void decrement_active_connections(void); 46 | int get_active_connections(void); 47 | 48 | extern CONNECTIONS_TYPE active_connections; 49 | 50 | #endif // CLIENT_H 51 | -------------------------------------------------------------------------------- /include/eviction_policy.h: -------------------------------------------------------------------------------- 1 | #ifndef EVICTION_POLICY_H 2 | #define EVICTION_POLICY_H 3 | // Eviction policies 4 | typedef enum { 5 | EVICTION_TTL, 6 | EVICTION_LRU 7 | } eviction_policy_t; 8 | 9 | #endif //EVICTION_POLICY_H 10 | 11 | -------------------------------------------------------------------------------- /include/hashtable.h: -------------------------------------------------------------------------------- 1 | #ifndef HASHTABLE_H 2 | #define HASHTABLE_H 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Entry struct 12 | typedef struct hash_entry { 13 | char *key; 14 | void *value; 15 | struct hash_entry *next; 16 | 17 | } hash_entry_t; 18 | 19 | // HashTable 20 | typedef struct hash_table { 21 | int capacity; 22 | int size; 23 | hash_entry_t **table; 24 | //pthread_mutex_t hash_table_mutex; 25 | } hash_table_t; 26 | 27 | hash_table_t *hash_table_create(int capacity); 28 | //int hash_table_set(hash_table_t *ht, char *key, void *value, size_t size); 29 | int hash_table_set(hash_table_t *ht, char *key, void *value, size_t size, void (*cleanup_callback)(void *)); 30 | void *hash_table_get(hash_table_t *ht, char *key); 31 | void hash_table_print_keys(hash_table_t *ht); 32 | void hash_table_cleanup(hash_table_t *ht, void (*cleanup_callback)(void *)); 33 | int hash_table_remove(hash_table_t *ht, char *key, void (*cleanup_callback)(void *)); 34 | void hash_table_set_resize_flag(int enabled); 35 | 36 | // Consider indirect testing for this so I can make it static 37 | #ifdef TESTING 38 | int hash_table_resize(hash_table_t *ht); 39 | #endif 40 | 41 | // Features flags 42 | extern int hash_table_resize_fc_enabled; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/log_level.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_LEVEL_H 2 | #define LOG_LEVEL_H 3 | // Log levels 4 | typedef enum { 5 | LOG_LEVEL_DEBUG, 6 | LOG_LEVEL_INFO, 7 | LOG_LEVEL_WARN, 8 | LOG_LEVEL_ERROR, 9 | LOG_LEVEL_NONE // no logs 10 | } log_level_t; 11 | 12 | #endif //LOG_LEVEL_H 13 | -------------------------------------------------------------------------------- /include/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGGER_H 2 | #define LOGGER_H 3 | 4 | #include 5 | #include "log_level.h" 6 | 7 | // Color definitions 8 | #define ANSI_COLOR_RED "\033[31m" 9 | #define ANSI_COLOR_GREEN "\033[32m" 10 | #define ANSI_COLOR_YELLOW "\033[33m" 11 | #define ANSI_COLOR_BLUE "\033[34m" 12 | #define ANSI_COLOR_MAGENTA "\033[35m" 13 | #define ANSI_COLOR_CYAN "\033[36m" 14 | #define ANSI_COLOR_RESET "\033[0m" 15 | 16 | // Functions 17 | void log_info(const char* format, ...); 18 | void log_warn(const char* format, ...); 19 | void log_error(const char* format, ...); 20 | void log_debug(const char* format, ...); 21 | 22 | 23 | #endif // LOGGER_H 24 | 25 | -------------------------------------------------------------------------------- /include/lru_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef LRU_MANAGER_H 2 | #define LRU_MANAGER_H 3 | 4 | #include "hashtable.h" 5 | 6 | typedef struct lru_entry { 7 | char* key; 8 | void *value; 9 | struct lru_entry *previous; 10 | struct lru_entry *next; 11 | 12 | } lru_entry_t; 13 | 14 | typedef struct lru_manager { 15 | hash_table_t *hash_table_main; 16 | long size; 17 | long capacity; 18 | lru_entry_t *head; 19 | lru_entry_t *tail; 20 | 21 | } lru_manager_t; 22 | 23 | 24 | void custom_cleanup_lru(void *arg); 25 | void lru_set(char *key, char *value, char *response); 26 | void lru_get(char *key, char *response); 27 | void lru_delete(char *key, char *response); 28 | 29 | extern lru_manager_t* lru_manager; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /include/mock_malloc.h: -------------------------------------------------------------------------------- 1 | #ifndef MOCK_MALLOC_H 2 | #define MOCK_MALLOC_H 3 | 4 | #include 5 | 6 | void set_malloc_fail(int fail); 7 | 8 | void* mock_malloc(size_t size); 9 | void* mock_calloc(size_t nmemb, size_t size); 10 | 11 | #ifdef TESTING 12 | #define malloc(size) mock_malloc(size) 13 | #define calloc(nmemb, size) mock_calloc(nmemb, size) 14 | #endif 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /include/response.h: -------------------------------------------------------------------------------- 1 | #ifndef RESPONSE_H 2 | #define RESPONSE_H 3 | 4 | // Functions 5 | void write_response_str(char *response, char *response_value); 6 | void write_response_int(char *response, int response_value); 7 | 8 | // This function pointer helps with the unit testing 9 | // typedef void (*response_writer_t)(char *response, char *response_value); 10 | // void set_response_writer(response_writer_t writer); 11 | // extern response_writer_t response_writer; 12 | 13 | #endif // RESPONSE_H 14 | -------------------------------------------------------------------------------- /include/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H 2 | #define SERVER_H 3 | 4 | #include "common.h" 5 | #include 6 | 7 | typedef enum { 8 | SERVER_STATE_INITIALIZING, 9 | SERVER_STATE_RUNNING, 10 | SERVER_STATE_STOPPED 11 | } server_state_t; 12 | 13 | int run_server(int port); 14 | 15 | extern atomic_int server_state; 16 | void set_server_state(server_state_t state); 17 | server_state_t get_server_state(void); 18 | 19 | #endif // SERVER_H 20 | -------------------------------------------------------------------------------- /include/signal_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef SIGNAL_HANDLER_H 2 | #define SIGNAL_HANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef TESTING 9 | //#warning "TESTING macro is defined" 10 | #define KEEP_RUNNING_TYPE atomic_int 11 | #define KEEP_RUNNING_INIT 1 12 | #define KEEP_RUNNING_STORE(var, val) atomic_store(&(var), (val)) 13 | #define KEEP_RUNNING_LOAD(var) atomic_load(&(var)) 14 | #else 15 | #define KEEP_RUNNING_TYPE volatile sig_atomic_t 16 | #define KEEP_RUNNING_INIT 1 17 | #define KEEP_RUNNING_STORE(var, val) (var = (val)) 18 | #define KEEP_RUNNING_LOAD(var) (var) 19 | #endif 20 | 21 | void set_event_loop_state(int state); 22 | void handle_shutdown_signal(int signal_number); 23 | void setup_signal_handling(void); 24 | extern KEEP_RUNNING_TYPE keep_running; 25 | 26 | #endif // SIGNAL_HANDLER_H 27 | -------------------------------------------------------------------------------- /include/ttl_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef TTL_MANAGER_H 2 | #define TTL_MANAGER_H 3 | 4 | #include 5 | #include "hashtable.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../include/config.h" 13 | #include "../include/logger.h" 14 | #include "../include/common.h" 15 | #include "../include/response.h" 16 | 17 | typedef struct ttl_entry { 18 | void *value; 19 | time_t timestamp; 20 | int ttl; 21 | } ttl_entry_t; 22 | 23 | typedef struct ttl_manager { 24 | hash_table_t *hash_table_main; 25 | } ttl_manager_t; 26 | 27 | 28 | // Functions 29 | void ttl_set(char *key, char *value, char *ttl_value, char *response); 30 | void ttl_get(char *key, char *response); 31 | void ttl_delete(char *key, char *response); 32 | void custom_cleanup_ttl(void *arg); 33 | bool is_entry_expired(ttl_entry_t *entry, time_t current_time); 34 | 35 | // Vars 36 | extern ttl_manager_t* ttl_manager; 37 | 38 | #endif // TTL_MANAGER_H 39 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | run_tests() { 4 | local policy=$1 5 | local output_file_memory="test_output_mem_$policy.log" 6 | local output_file_thread="test_output_thread_$policy.log" 7 | 8 | echo "========================================" 9 | echo "Running tests with eviction policy: $policy" 10 | echo "========================================" 11 | 12 | make clean 13 | 14 | if ! make EVICTION_POLICY=$policy; then 15 | echo "Build failed for policy: $policy" 16 | exit 1 17 | fi 18 | 19 | echo "Running Valgrind Memory Leak Check..." 20 | if ! valgrind --leak-check=full --track-origins=yes -s --show-leak-kinds=all --show-reachable=yes ./test_app &> $output_file_memory; then 21 | echo "Valgrind Memory Check failed for policy: $policy" 22 | exit 1 23 | fi 24 | 25 | if ! grep -q "Tests 0 Failures 0 Ignored" $output_file_memory; then 26 | echo "Test failures detected for policy: $policy" 27 | cat $output_file_memory 28 | exit 1 29 | fi 30 | 31 | if ! grep -q "All heap blocks were freed -- no leaks are possible" $output_file_memory; then 32 | echo "Test failures detected for policy: $policy" 33 | cat $output_file_memory 34 | exit 1 35 | fi 36 | 37 | echo "Memory Leak Check passed for policy: $policy" 38 | 39 | echo "Running Valgrind Helgrind (Thread Error Check)..." 40 | if ! valgrind --tool=helgrind --history-level=full ./test_app &> $output_file_thread; then 41 | echo "Valgrind Helgrind Check failed for policy: $policy" 42 | exit 1 43 | fi 44 | 45 | if ! grep -q "ERROR SUMMARY: 0 errors from 0 contexts" $output_file_thread; then 46 | echo "Valgrind Helgrind Check failed for policy: $policy" 47 | cat $output_file_thread 48 | exit 1 49 | fi 50 | 51 | echo "========================================" 52 | echo "Tests passed for eviction policy: $policy" 53 | echo "========================================" 54 | } 55 | 56 | # run tests 57 | run_tests "TTL" 58 | run_tests "LRU" 59 | 60 | echo "================================================" 61 | echo "= =" 62 | echo "= =" 63 | echo "= All tests passed for both eviction policies! =" 64 | echo "= No memory leaks or thread issues detected! =" 65 | echo "= =" 66 | echo "= =" 67 | echo "================================================" 68 | 69 | -------------------------------------------------------------------------------- /src/connection.c: -------------------------------------------------------------------------------- 1 | #include "../include/connection.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // response_writer_t response_writer = write_response_str; 8 | // void set_response_writer(response_writer_t writer) { response_writer = 9 | // writer; } 10 | void increment_active_connections(void) { 11 | CONNECTIONS_INCREMENT(active_connections); 12 | } 13 | void decrement_active_connections(void) { 14 | CONNECTIONS_DECREMENT(active_connections); 15 | } 16 | int get_active_connections(void) { return CONNECTIONS_GET(active_connections); } 17 | 18 | static void connections_cmd(char *response) { 19 | write_response_int(response, get_active_connections()); 20 | } 21 | 22 | static void keys_num_ttl_cmd(char *response) { 23 | write_response_int(response, ttl_manager->hash_table_main->size); 24 | } 25 | static void keys_num_lru_cmd(char *response) { 26 | write_response_int(response, lru_manager->hash_table_main->size); 27 | } 28 | // Remove client from the linked list 29 | void remove_client_from_list(node_data_t *client_data) { 30 | if (client_data && client_data->data.client) { 31 | close(client_data->data.client->fd); 32 | free(client_data->data.client); 33 | client_data->data.client = NULL; 34 | } 35 | if (client_data) { 36 | free(client_data); 37 | client_data = NULL; 38 | } 39 | decrement_active_connections(); 40 | return; 41 | } 42 | 43 | // Remove a client from the epoll, so it wont track it 44 | void delete_resources(int epoll_fd, client_t *client, struct epoll_event *ev) { 45 | if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client->fd, NULL) == -1) { 46 | perror("epoll_ctl: EPOLL_CTL_DEL failed"); 47 | } 48 | 49 | remove_client_from_list((node_data_t *)ev->data.ptr); 50 | } 51 | 52 | static void set_error_msg(int epoll_fd, client_t *client, 53 | struct epoll_event *ev, const char *error_response) { 54 | int response_len = strlen(error_response); 55 | 56 | if (client->write_buffer_len + response_len < BUFFER_SIZE) { 57 | memcpy(client->write_buffer + client->write_buffer_len, error_response, 58 | response_len); 59 | client->write_buffer_len += response_len; 60 | 61 | client->close_after_write = 1; 62 | 63 | ev->events = EPOLLOUT | EPOLLET | EPOLLHUP | EPOLLERR | EPOLLRDHUP; 64 | if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, client->fd, ev) == -1) { 65 | perror("Epoll ctl mod failed"); 66 | delete_resources(epoll_fd, client, ev); 67 | } 68 | } else { 69 | fprintf(stderr, "Write buffer overflow\n"); 70 | delete_resources(epoll_fd, client, ev); 71 | } 72 | } 73 | 74 | static void process_command_for_lru(char *command, char *response) { 75 | char command_type[20] = {0}; 76 | char key[BUFFER_SIZE] = {0}; 77 | char value[BUFFER_SIZE] = {0}; 78 | 79 | int num_args = sscanf(command, "%s %s %s", command_type, key, value); 80 | 81 | log_debug("Command arguments num: %ld", num_args); 82 | log_debug("command_type: %.3s", command_type); 83 | 84 | if (strncmp(command_type, "SET", 3) == 0 && num_args == 3) { 85 | lru_set(key, value, response); 86 | 87 | } else if (strncmp(command_type, "GET", 3) == 0 && num_args == 2) { 88 | lru_get(key, response); 89 | 90 | } else if (strncmp(command_type, "DELETE", 6) == 0 && num_args == 2) { 91 | lru_delete(key, response); 92 | 93 | } else if (strncmp(command_type, "CONNECTIONS", 11) == 0 && num_args == 1) { 94 | connections_cmd(response); 95 | 96 | } else if (strncmp(command_type, "KEYS_NUM", 8) == 0 && num_args == 1) { 97 | keys_num_lru_cmd(response); 98 | 99 | } else { 100 | // Unknown command 101 | char *response_value = "ERROR: UNKNOWN OR MALFORMED COMMAND"; 102 | write_response_str(response, response_value); 103 | } 104 | } 105 | 106 | static void process_command_for_ttl(char *command, char *response) { 107 | char command_type[20] = {0}; 108 | char key[BUFFER_SIZE] = {0}; 109 | char value[BUFFER_SIZE] = {0}; 110 | char ttl_value[BUFFER_SIZE_TTL] = {0}; 111 | 112 | int num_args = 113 | sscanf(command, "%s %s %s %s", command_type, key, value, ttl_value); 114 | 115 | log_debug("Command arguments num: %ld", num_args); 116 | log_debug("command_type: %.3s", command_type); 117 | 118 | if (strncmp(command_type, "SET", 3) == 0 && num_args == 4) { 119 | ttl_set(key, value, ttl_value, response); 120 | 121 | } else if (strncmp(command_type, "GET", 3) == 0 && num_args == 2) { 122 | ttl_get(key, response); 123 | 124 | } else if (strncmp(command_type, "DELETE", 6) == 0 && num_args == 2) { 125 | ttl_delete(key, response); 126 | 127 | } else if (strncmp(command_type, "CONNECTIONS", 11) == 0 && num_args == 1) { 128 | connections_cmd(response); 129 | 130 | } else if (strncmp(command_type, "KEYS_NUM", 8) == 0 && num_args == 1) { 131 | keys_num_ttl_cmd(response); 132 | 133 | } else { 134 | // Unknown command 135 | char *response_value = "ERROR: UNKNOWN OR MALFORMED COMMAND"; 136 | write_response_str(response, response_value); 137 | } 138 | } 139 | 140 | void write_response_str(char *response, char *response_value) { 141 | // Consider the \r\n\0 142 | if (strlen(response_value) >= BUFFER_SIZE - 3) { 143 | response_value = "ERROR: RESPONSE OVERFLOW"; 144 | } 145 | snprintf(response, BUFFER_SIZE, "%s\r\n", response_value); 146 | log_debug("Processing command response: %s\n", response); 147 | } 148 | 149 | void write_response_int(char *response, int response_value) { 150 | snprintf(response, BUFFER_SIZE, "%d\r\n", response_value); 151 | log_debug("Processing command response: %s\n", response); 152 | } 153 | 154 | static void process_command(char *command, char *response) { 155 | log_debug("Processing command: %s\n", command); 156 | 157 | if (EVICTION == EVICTION_TTL) { 158 | log_debug("In EVICTION_TTL mode\n"); 159 | process_command_for_ttl(command, response); 160 | 161 | } else if (EVICTION == EVICTION_LRU) { 162 | log_debug("In EVICTION_LRU mode\n"); 163 | process_command_for_lru(command, response); 164 | } else { 165 | log_error("UNKNOWN EVICTION POLICY mode\n"); 166 | } 167 | } 168 | 169 | void handle_client_read(client_t *client, struct epoll_event *ev, 170 | int epoll_fd) { 171 | char temp_buffer[BUFFER_SIZE]; 172 | int bytes_read = read(client->fd, temp_buffer, sizeof(temp_buffer)); 173 | 174 | log_debug("Received message: %.*s\n", bytes_read, temp_buffer); 175 | 176 | if (bytes_read > 0) { 177 | // Accumulate read data 178 | if (client->read_buffer_len + bytes_read < BUFFER_SIZE) { 179 | memcpy(client->read_buffer + client->read_buffer_len, temp_buffer, 180 | bytes_read); 181 | client->read_buffer_len += bytes_read; 182 | 183 | // Ensure the string is terminated 184 | if (client->read_buffer_len < BUFFER_SIZE) { 185 | client->read_buffer[client->read_buffer_len] = '\0'; 186 | } 187 | 188 | // Check if the buffer contains at least one \r\n 189 | char *first_rn = strstr(client->read_buffer, "\r\n"); 190 | 191 | // If a \r\n exists, ensure there's only one and process the 192 | // command 193 | if (first_rn != NULL) { 194 | // Check if there is any data after the first \r\n 195 | if (first_rn + 2 < 196 | client->read_buffer + client->read_buffer_len) { 197 | // There's more data after the first \r\n, this as an 198 | // error 199 | const char *error_response = 200 | "ERROR: Multiple commands in one request\r\n"; 201 | set_error_msg(epoll_fd, client, ev, error_response); 202 | return; 203 | } 204 | 205 | // Happy path 206 | int command_length = first_rn - client->read_buffer; 207 | char command[BUFFER_SIZE] = {0}; 208 | strncpy(command, client->read_buffer, command_length); 209 | command[command_length] = '\0'; 210 | 211 | char response[BUFFER_SIZE]; 212 | process_command(command, response); 213 | int response_len = strlen(response); 214 | 215 | // Check if the write buffer has enough space for the 216 | // response 217 | if (client->write_buffer_len + response_len < BUFFER_SIZE) { 218 | memcpy(client->write_buffer + client->write_buffer_len, 219 | response, response_len); 220 | client->write_buffer_len += response_len; 221 | } else { 222 | fprintf(stderr, "Write buffer overflow\n"); 223 | delete_resources(epoll_fd, client, ev); 224 | return; 225 | } 226 | 227 | client->read_buffer_len = 0; 228 | 229 | // Change the event to monitor for writing 230 | ev->events = 231 | EPOLLOUT | EPOLLET | EPOLLHUP | EPOLLERR | EPOLLRDHUP; 232 | if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, client->fd, ev) == -1) { 233 | perror("Epoll ctl mod failed"); 234 | delete_resources(epoll_fd, client, ev); 235 | return; 236 | } 237 | 238 | } else if (client->read_buffer_len == BUFFER_SIZE - 1) { 239 | // Buffer is full but no complete command, this is an error 240 | const char *error_response = "ERROR: Command incomplete\r\n"; 241 | set_error_msg(epoll_fd, client, ev, error_response); 242 | return; 243 | } 244 | } else { 245 | // Buffer overflow, the command is too large 246 | const char *error_response = "ERROR: Command too large\r\n"; 247 | set_error_msg(epoll_fd, client, ev, error_response); 248 | return; 249 | } 250 | 251 | } else if (bytes_read == 0) { 252 | // Handle disconnect 253 | log_debug("Client disconnected: %p\n", client); 254 | delete_resources(epoll_fd, client, ev); 255 | } else if (bytes_read < 0 && errno != EAGAIN) { 256 | // Handle read error 257 | perror("Read error"); 258 | delete_resources(epoll_fd, client, ev); 259 | } 260 | } 261 | 262 | void handle_client_write(client_t *client, struct epoll_event *ev, 263 | int epoll_fd) { 264 | while (client->write_buffer_pos < client->write_buffer_len) { 265 | int bytes_written = 266 | write(client->fd, client->write_buffer + client->write_buffer_pos, 267 | client->write_buffer_len - client->write_buffer_pos); 268 | if (bytes_written < 0) { 269 | if (errno == EAGAIN) { 270 | // Socket not ready for writing, retry later 271 | 272 | ev->events |= EPOLLOUT; 273 | if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, client->fd, ev) == -1) { 274 | perror("epoll_ctl: EPOLL_CTL_MOD failed"); 275 | delete_resources(epoll_fd, client, ev); 276 | } 277 | return; 278 | } else { 279 | perror("Write error"); 280 | delete_resources(epoll_fd, client, ev); 281 | return; 282 | } 283 | } 284 | client->write_buffer_pos += bytes_written; 285 | } 286 | 287 | // Stop monitoring for writable events 288 | if (client->write_buffer_pos == client->write_buffer_len) { 289 | client->write_buffer_pos = 0; 290 | 291 | client->write_buffer_len = 0; 292 | ev->events = EPOLLIN | EPOLLET | EPOLLHUP | EPOLLERR | EPOLLRDHUP; 293 | 294 | if (client->close_after_write) { 295 | delete_resources(epoll_fd, client, ev); 296 | return; 297 | } 298 | 299 | if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, client->fd, ev) == -1) { 300 | perror("Epoll ctl mod failed"); 301 | delete_resources(epoll_fd, client, ev); 302 | return; 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/hashtable.c: -------------------------------------------------------------------------------- 1 | // #include 2 | #include 3 | #ifdef TESTING 4 | // #warning "TESTING macro is defined" 5 | #include "../include/mock_malloc.h" 6 | #endif 7 | 8 | #include "../include/hashtable.h" 9 | 10 | int hash_table_resize(hash_table_t *ht); 11 | int hash_table_resize_fc_enabled = 1; 12 | 13 | // pthread_mutex_t hash_table_mutex = PTHREAD_MUTEX_INITIALIZER; 14 | // init table 15 | hash_table_t *hash_table_create(int capacity) { 16 | hash_table_t *ht = malloc(sizeof(hash_table_t)); 17 | if (!ht) { 18 | printf("failed to allocate memory: %s", strerror(errno)); 19 | exit(EXIT_FAILURE); 20 | } 21 | 22 | ht->table = malloc(sizeof(hash_entry_t *) * capacity); 23 | if (!ht->table) { 24 | printf("failed to allocate memory: %s", strerror(errno)); 25 | free(ht); 26 | exit(EXIT_FAILURE); 27 | } 28 | 29 | ht->capacity = capacity; 30 | ht->size = 0; 31 | 32 | for (int i = 0; i < capacity; i++) { 33 | ht->table[i] = NULL; 34 | } 35 | 36 | return ht; 37 | } 38 | 39 | // Hash djb2 40 | static int hash(char *key, int capacity) { 41 | unsigned long hash = 5381; 42 | int c; 43 | 44 | while ((c = *key++)) { 45 | hash = ((hash << 5) + hash) + c; // hash * 33 + c 46 | } 47 | 48 | // printf("address: %d\n", hash% TABLE_SIZE); 49 | 50 | return hash % capacity; 51 | } 52 | 53 | // Set 54 | int hash_table_set(hash_table_t *ht, char *key, void *value, size_t size, 55 | void (*cleanup_callback)(void *)) { 56 | if (key == NULL || value == NULL) { 57 | printf("invalid memory reference\n"); 58 | return -1; 59 | } 60 | // printf("init size: %d\n", ht->size); 61 | if ((ht->size >= (ht->capacity * 0.75)) && hash_table_resize_fc_enabled) { 62 | // printf("size: %d cap: %d\n", ht->size, ht->capacity / 2); 63 | int result = hash_table_resize(ht); 64 | if (result != 0) { 65 | return -1; 66 | } 67 | } 68 | int address = hash(key, ht->capacity); 69 | 70 | hash_entry_t *entry = ht->table[address]; 71 | 72 | while (entry != NULL) { 73 | if (strcmp(entry->key, key) == 0) { 74 | // printf("entry key: %s key: %s\n", entry->Key, key); 75 | 76 | void *new_value = malloc(size); 77 | 78 | if (!new_value) { 79 | printf("failed to allocate memory: %s\n", strerror(errno)); 80 | return -1; 81 | } 82 | // free the old value only if the allocation succeeds to 83 | // prevent data loss 84 | memcpy(new_value, value, size); 85 | 86 | // check if special handling is needed 87 | if (cleanup_callback) { 88 | cleanup_callback(entry->value); 89 | } 90 | free(entry->value); 91 | 92 | entry->value = new_value; 93 | 94 | return 0; // Success 95 | } 96 | entry = entry->next; 97 | } 98 | 99 | entry = malloc(sizeof(hash_entry_t)); // consider also calloc 100 | if (!entry) { 101 | printf("failed to allocate memory: %s\n", strerror(errno)); 102 | return -1; 103 | } 104 | entry->key = strdup(key); 105 | // entry->value = strdup(value); 106 | entry->value = malloc(size); 107 | if (!entry->key || !entry->value) { 108 | printf("failed to allocate memory for key or value: %s\n", 109 | strerror(errno)); 110 | free(entry->key); 111 | free(entry->value); 112 | free(entry); 113 | return -1; // Error 114 | } 115 | memcpy(entry->value, value, size); 116 | 117 | entry->next = ht->table[address]; 118 | ht->table[address] = entry; 119 | 120 | ht->size++; 121 | // printf("---increase size: %d\n", ht->size); 122 | 123 | // consider returning the value, in our case it will be lru_entry_t* 124 | // return entry->value; 125 | return 0; 126 | } 127 | 128 | // Get 129 | void *hash_table_get(hash_table_t *ht, char *key) { 130 | int address = hash(key, ht->capacity); 131 | hash_entry_t *entry = ht->table[address]; 132 | 133 | while (entry != NULL) { 134 | if (strcmp(entry->key, key) == 0) { 135 | return entry->value; 136 | } 137 | entry = entry->next; 138 | } 139 | 140 | return NULL; 141 | } 142 | 143 | int hash_table_remove(hash_table_t *ht, char *key, 144 | void (*cleanup_callback)(void *)) { 145 | int address = hash(key, ht->capacity); 146 | 147 | hash_entry_t *current_entry = ht->table[address]; 148 | hash_entry_t *prev_entry = NULL; 149 | 150 | while (current_entry != NULL) { 151 | if (strcmp(current_entry->key, key) == 0) { 152 | if (prev_entry == NULL) { 153 | ht->table[address] = current_entry->next; 154 | } else { 155 | prev_entry->next = current_entry->next; 156 | } 157 | free(current_entry->key); 158 | // check if special handling is needed 159 | if (cleanup_callback) { 160 | cleanup_callback(current_entry->value); 161 | } 162 | free(current_entry->value); 163 | free(current_entry); 164 | ht->size--; 165 | return 0; // Success 166 | } 167 | prev_entry = current_entry; 168 | current_entry = current_entry->next; 169 | } 170 | 171 | return -1; // Key not found 172 | } 173 | 174 | // resize 175 | // Avoid the use of realloc here because you will end up reading and modifying 176 | // the same table... 177 | int hash_table_resize(hash_table_t *ht) { 178 | // printf("resize initiated\n"); 179 | int new_capacity_no = 2 * ht->capacity; 180 | hash_entry_t **new_table = 181 | (hash_entry_t **)malloc(new_capacity_no * sizeof(hash_entry_t *)); 182 | if (new_table == NULL) { 183 | printf("failed to allocate memory for new table: %s\n", 184 | strerror(errno)); 185 | return -1; 186 | } 187 | 188 | for (int i = 0; i < new_capacity_no; i++) { 189 | new_table[i] = NULL; 190 | } 191 | 192 | // Re-hash 193 | for (int i = 0; i < ht->capacity; i++) { 194 | hash_entry_t *entry = ht->table[i]; 195 | while (entry != NULL) { 196 | hash_entry_t *next = entry->next; 197 | int address = hash(entry->key, new_capacity_no); 198 | entry->next = new_table[address]; 199 | new_table[address] = entry; 200 | entry = next; 201 | } 202 | } 203 | 204 | free(ht->table); 205 | ht->table = new_table; 206 | ht->capacity = new_capacity_no; 207 | 208 | // printf("resize finished\n"); 209 | return 0; 210 | } 211 | 212 | // Keys 213 | void hash_table_print_keys(hash_table_t *ht) { 214 | printf("Keys:\n"); 215 | printf("capacity: %d\n", ht->capacity); 216 | for (int i = 0; i < ht->capacity; i++) { 217 | hash_entry_t *entry = ht->table[i]; 218 | if (entry == NULL) { 219 | continue; 220 | } 221 | 222 | while (entry != NULL) { 223 | printf("position: %d key: %s\n", i, entry->key); 224 | entry = entry->next; 225 | } 226 | } 227 | } 228 | 229 | // CleanUp 230 | void hash_table_cleanup(hash_table_t *ht, void (*cleanup_callback)(void *)) { 231 | for (int i = 0; i < ht->capacity; i++) { 232 | hash_entry_t *entry = ht->table[i]; 233 | if (entry == NULL) { 234 | continue; 235 | } 236 | while (entry != NULL) { 237 | hash_entry_t *tmp = entry->next; 238 | // check if special handling is needed 239 | if (cleanup_callback) { 240 | cleanup_callback(entry->value); 241 | } 242 | free(entry->value); 243 | free(entry->key); 244 | free(entry); 245 | entry = tmp; 246 | } 247 | } 248 | free(ht->table); 249 | free(ht); 250 | } 251 | 252 | void hash_table_set_resize_flag(int enabled) { 253 | hash_table_resize_fc_enabled = enabled; 254 | } 255 | -------------------------------------------------------------------------------- /src/logger.c: -------------------------------------------------------------------------------- 1 | #include "../include/logger.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../include/config.h" 10 | 11 | static void get_current_time(char* buffer, size_t buffer_size) { 12 | time_t now = time(NULL); 13 | struct tm* t = localtime(&now); 14 | strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S", t); 15 | // Add milliseconds 16 | struct timeval tv; 17 | gettimeofday(&tv, NULL); 18 | size_t len = strlen(buffer); 19 | snprintf(buffer + len, buffer_size - len, ".%03ld", tv.tv_usec / 1000); 20 | } 21 | 22 | static void prod_log(log_level_t level, const char* level_str, 23 | const char* color, const char* format, va_list args) { 24 | if (level >= LOG_LEVEL) { 25 | char time_buffer[30]; 26 | get_current_time(time_buffer, sizeof(time_buffer)); 27 | 28 | printf("%s[%s] %s: ", color, time_buffer, level_str); 29 | vprintf(format, args); 30 | printf("%s\n", ANSI_COLOR_RESET); 31 | } 32 | } 33 | 34 | void log_info(const char* format, ...) { 35 | va_list args; 36 | va_start(args, format); 37 | prod_log(LOG_LEVEL_INFO, "INFO", ANSI_COLOR_GREEN, format, args); 38 | va_end(args); 39 | } 40 | 41 | void log_warn(const char* format, ...) { 42 | va_list args; 43 | va_start(args, format); 44 | prod_log(LOG_LEVEL_WARN, "WARN", ANSI_COLOR_YELLOW, format, args); 45 | va_end(args); 46 | } 47 | 48 | void log_error(const char* format, ...) { 49 | va_list args; 50 | va_start(args, format); 51 | prod_log(LOG_LEVEL_ERROR, "ERROR", ANSI_COLOR_RED, format, args); 52 | va_end(args); 53 | } 54 | 55 | void log_debug(const char* format, ...) { 56 | va_list args; 57 | va_start(args, format); 58 | prod_log(LOG_LEVEL_DEBUG, "DEBUG", ANSI_COLOR_CYAN, format, args); 59 | va_end(args); 60 | } 61 | -------------------------------------------------------------------------------- /src/lru_manager.c: -------------------------------------------------------------------------------- 1 | #include "../include/lru_manager.h" 2 | 3 | #include 4 | 5 | // #include "../include/config.h" 6 | #include "../include/logger.h" 7 | #include "../include/response.h" 8 | 9 | // start private functions 10 | // static void print_lru_queue(void); 11 | // end private functions 12 | 13 | void custom_cleanup_lru(void *arg) { 14 | lru_entry_t *lru_entry = arg; 15 | if (lru_entry && lru_entry->value && lru_entry->key) { 16 | free(lru_entry->value); 17 | free(lru_entry->key); 18 | } 19 | } 20 | 21 | void add_entry_to_front_of_q(lru_entry_t *entry) { 22 | if (lru_manager->head == NULL) { 23 | lru_manager->head = entry; 24 | lru_manager->tail = entry; 25 | return; 26 | } 27 | entry->next = lru_manager->head; 28 | lru_manager->head->previous = entry; 29 | lru_manager->head = entry; 30 | } 31 | 32 | // It removes all the references from the Q but doesn't delete/free the entry 33 | void remove_entry_from_q(lru_entry_t *entry) { 34 | if (entry->previous != NULL) { 35 | entry->previous->next = entry->next; 36 | } else { 37 | // If it doesn't have previous then it is the head 38 | lru_manager->head = entry->next; 39 | } 40 | 41 | if (entry->next != NULL) { 42 | entry->next->previous = entry->previous; 43 | } else { 44 | // It means it is the tail 45 | lru_manager->tail = entry->previous; 46 | } 47 | 48 | entry->previous = NULL; 49 | entry->next = NULL; 50 | } 51 | 52 | // It removes and deletes the entry completely from the Q 53 | // void delete_entry_from_q(lru_entry_t *entry) { 54 | // remove_entry_from_q(entry); 55 | // free(entry); 56 | // } 57 | 58 | void move_entry_to_front_of_q(lru_entry_t *entry) { 59 | if (lru_manager->head == entry) { 60 | return; 61 | } 62 | // doesn't free the entry, only removes the refernces 63 | remove_entry_from_q(entry); 64 | add_entry_to_front_of_q(entry); 65 | } 66 | 67 | // lru_set 68 | void lru_set(char *key, char *value, char *response) { 69 | char *response_value = NULL; 70 | log_debug("key: %s\n", key); 71 | log_debug("value: %s\n", value); 72 | 73 | lru_entry_t *lru_entry = hash_table_get(lru_manager->hash_table_main, key); 74 | if (lru_entry != NULL) { 75 | // move to front of list 76 | move_entry_to_front_of_q(lru_entry); 77 | 78 | // change the value 79 | void *new_value_ptr = strdup(value); 80 | if (!new_value_ptr) { 81 | fprintf(stderr, 82 | "failed to allocate memory for new value during lru_set"); 83 | response_value = "ERROR: MEMORY ALLOC FAILURE"; 84 | write_response_str(response, response_value); 85 | return; 86 | } 87 | // free the old value 88 | free(lru_entry->value); 89 | // set the new value to the entry 90 | lru_entry->value = new_value_ptr; 91 | 92 | // send response 93 | response_value = "OK"; 94 | write_response_str(response, response_value); 95 | return; 96 | } 97 | 98 | // key doesn't exist, so create a new and store it 99 | 100 | lru_entry_t new_lru_entry; 101 | new_lru_entry.value = strdup(value); 102 | if (!new_lru_entry.value) { 103 | fprintf(stderr, 104 | "failed to allocate memory during setting new entry value in " 105 | "lru_set"); 106 | response_value = "ERROR: MEMORY ALLOC FAILURE"; 107 | write_response_str(response, response_value); 108 | return; 109 | } 110 | new_lru_entry.key = strdup(key); 111 | if (!new_lru_entry.key) { 112 | free(new_lru_entry.value); 113 | fprintf(stderr, 114 | "failed to allocate memory during setting new entry key in " 115 | "lru_set"); 116 | response_value = "ERROR: MEMORY ALLOC FAILURE"; 117 | write_response_str(response, response_value); 118 | return; 119 | } 120 | new_lru_entry.next = NULL; 121 | new_lru_entry.previous = NULL; 122 | 123 | // check if it fits in the current capacity and if not remove the last entry 124 | // from the Q 125 | if (lru_manager->size >= lru_manager->capacity) { 126 | // remove it from the Q 127 | lru_entry_t *to_be_removed = lru_manager->tail; 128 | remove_entry_from_q(to_be_removed); 129 | // remove the last entry from hashtable 130 | hash_table_remove(lru_manager->hash_table_main, to_be_removed->key, 131 | custom_cleanup_lru); 132 | lru_manager->size--; 133 | } 134 | 135 | int error = 136 | hash_table_set(lru_manager->hash_table_main, key, &new_lru_entry, 137 | sizeof(new_lru_entry), custom_cleanup_lru); 138 | if (error != 0) { 139 | fprintf(stderr, "failed to allocate memory during lru set_value"); 140 | response_value = "ERROR: MEMORY ALLOC FAILURE"; 141 | write_response_str(response, response_value); 142 | return; 143 | } 144 | 145 | // This is neccesary because the hashtable recreates it's own entries and we 146 | // want the actual created entry. Can be optimized by modifing the hashtable 147 | // to return the value when we set a new entry 148 | lru_entry_t *lru_entry_new = 149 | hash_table_get(lru_manager->hash_table_main, key); 150 | if (lru_entry_new == NULL) { 151 | fprintf(stderr, "failed, something went terribly wrong! "); 152 | response_value = "ERROR: UNEXPECTED FAILURE ON RETRIEVING THE KEY"; 153 | write_response_str(response, response_value); 154 | return; 155 | } 156 | 157 | add_entry_to_front_of_q(lru_entry_new); 158 | lru_manager->size++; 159 | 160 | // hash_table_print_keys(lru_manager->hash_table_main); 161 | // print_lru_queue(); 162 | 163 | response_value = "OK"; 164 | write_response_str(response, response_value); 165 | } 166 | 167 | // lru_get 168 | void lru_get(char *key, char *response) { 169 | char *response_value = NULL; 170 | lru_entry_t *lru_entry = hash_table_get(lru_manager->hash_table_main, key); 171 | 172 | if (lru_entry == NULL) { 173 | response_value = "ERROR: KEY NOT FOUND"; 174 | write_response_str(response, response_value); 175 | return; 176 | } 177 | 178 | response_value = lru_entry->value; 179 | move_entry_to_front_of_q(lru_entry); 180 | 181 | // log_debug("lru capacity: %d", lru_manager->capacity); 182 | // hash_table_print_keys(lru_manager->hash_table_main); 183 | // print_lru_queue(); 184 | 185 | write_response_str(response, response_value); 186 | } 187 | 188 | // lru_delete 189 | void lru_delete(char *key, char *response) { 190 | char *response_value = NULL; 191 | lru_entry_t *lru_entry = hash_table_get(lru_manager->hash_table_main, key); 192 | if (lru_entry == NULL) { 193 | response_value = "ERROR: KEY NOT FOUND"; 194 | write_response_str(response, response_value); 195 | return; 196 | } 197 | remove_entry_from_q(lru_entry); 198 | hash_table_remove(lru_manager->hash_table_main, key, custom_cleanup_lru); 199 | lru_manager->size--; 200 | response_value = "OK"; 201 | 202 | // hash_table_print_keys(lru_manager->hash_table_main); 203 | // print_lru_queue(); 204 | 205 | write_response_str(response, response_value); 206 | } 207 | 208 | // static void print_lru_queue(void) { 209 | // char keys[200] = {0}; 210 | // if (lru_manager == NULL) { 211 | // log_debug("lru_manager is NULL"); 212 | // return; 213 | // } 214 | // lru_entry_t *current = lru_manager->head; 215 | // while (current != NULL) { 216 | // if (current->key) { 217 | // log_debug("queue item: %s", current->key); 218 | // strcat(keys, current->key); 219 | // } else { 220 | // log_debug("queue item has no key"); 221 | // } 222 | // current = current->next; 223 | // } 224 | // 225 | // log_debug("%s\n", keys); 226 | // } 227 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "../include/config.h" 2 | #include "../include/server.h" 3 | 4 | int main(void) { return run_server(PORT); } 5 | -------------------------------------------------------------------------------- /src/server.c: -------------------------------------------------------------------------------- 1 | #include "../include/server.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../include/config.h" 13 | #include "../include/connection.h" 14 | #include "../include/lru_manager.h" 15 | #include "../include/signal_handler.h" 16 | #include "../include/ttl_manager.h" 17 | 18 | CONNECTIONS_TYPE active_connections = CONNECTIONS_INIT; 19 | atomic_int server_state = SERVER_STATE_INITIALIZING; 20 | 21 | node_data_t *server_event = NULL; 22 | node_data_t *timer_event = NULL; 23 | // hash_table_t *hash_table_main = NULL; 24 | ttl_manager_t *ttl_manager = NULL; 25 | lru_manager_t *lru_manager = NULL; 26 | int timer_fd = 0; 27 | 28 | void hash_table_cleanup_expired(hash_table_t *ht); 29 | 30 | void set_server_state(server_state_t state) { 31 | atomic_store(&server_state, state); 32 | } 33 | server_state_t get_server_state(void) { return atomic_load(&server_state); } 34 | 35 | static void set_non_blocking(int socket) { 36 | int flags = fcntl(socket, F_GETFL, 0); 37 | fcntl(socket, F_SETFL, flags | O_NONBLOCK); 38 | } 39 | 40 | static int setup_server_socket(int port) { 41 | int server_fd; //, client_socket; 42 | struct sockaddr_in address = {0}; 43 | 44 | // Create a socket 45 | server_fd = socket(AF_INET, SOCK_STREAM, 0); 46 | if (server_fd == 0) { 47 | perror("Socket failed"); 48 | exit(EXIT_FAILURE); 49 | } 50 | 51 | address.sin_family = AF_INET; 52 | address.sin_addr.s_addr = INADDR_ANY; 53 | address.sin_port = htons(port); 54 | 55 | // Set the reuse flag 56 | int reuse = 1; 57 | if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, 58 | sizeof(int)) == -1) { 59 | perror("Set reuse failed"); 60 | close(server_fd); 61 | exit(EXIT_FAILURE); 62 | } 63 | // Bind 64 | if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { 65 | perror("Bind failed"); 66 | close(server_fd); 67 | exit(EXIT_FAILURE); 68 | } 69 | 70 | // Listen 71 | if (listen(server_fd, 3) < 0) { 72 | perror("Listen failed"); 73 | close(server_fd); 74 | exit(EXIT_FAILURE); 75 | } 76 | 77 | set_non_blocking(server_fd); 78 | 79 | return server_fd; 80 | } 81 | 82 | // A function that sets a timer using a file descriptor to trigger the cleanupof 83 | // the hashtable 84 | static int setup_cleanup_timerfd() { 85 | int tfd = timerfd_create(CLOCK_MONOTONIC, 0); 86 | if (tfd == -1) { 87 | perror("timerfd_create failed"); 88 | return -1; 89 | } 90 | struct itimerspec ts; 91 | ts.it_value.tv_sec = CLEAN_UP_TIME; // First trigger 92 | ts.it_value.tv_nsec = 0; 93 | ts.it_interval.tv_sec = CLEAN_UP_TIME; // Interval 94 | ts.it_interval.tv_nsec = 0; 95 | 96 | if (timerfd_settime(tfd, 0, &ts, NULL) == -1) { 97 | perror("timerfd_settime failed"); 98 | close(tfd); 99 | return -1; 100 | } 101 | 102 | log_info("CLEAN_UP_TIME: %d\n", CLEAN_UP_TIME); 103 | 104 | return tfd; 105 | } 106 | 107 | static int setup_epoll(int server_fd, int tfd) { 108 | int epoll_fd = epoll_create1(0); // a kernel obj that keeps track of 109 | // multiple fds and notifies on events 110 | if (epoll_fd == -1) { 111 | perror("Epoll create failed"); 112 | close(server_fd); 113 | exit(EXIT_FAILURE); 114 | } 115 | 116 | server_event = malloc(sizeof(node_data_t)); 117 | if (!server_event) { 118 | perror("malloc failed"); 119 | close(server_fd); 120 | exit(EXIT_FAILURE); 121 | } 122 | server_event->event_type = EVENT_SERVER; 123 | server_event->data.fd = server_fd; 124 | 125 | struct epoll_event ev; 126 | ev.events = EPOLLIN; // set flag to listen for read events on server socket 127 | ev.data.ptr = server_event; 128 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) { 129 | perror("Epoll ctl add failed"); 130 | close(server_fd); 131 | close(epoll_fd); 132 | close(timer_fd); 133 | free(server_event); 134 | server_event = NULL; 135 | exit(EXIT_FAILURE); 136 | } 137 | 138 | if (EVICTION == EVICTION_TTL) { 139 | // Add timer_fd to epoll 140 | struct epoll_event tev; 141 | tev.events = EPOLLIN; 142 | timer_event = malloc(sizeof(node_data_t)); 143 | if (!timer_event) { 144 | perror("malloc failed"); 145 | close(server_fd); 146 | close(timer_fd); 147 | close(epoll_fd); 148 | free(server_event); 149 | server_event = NULL; 150 | timer_event = NULL; 151 | exit(EXIT_FAILURE); 152 | } 153 | timer_event->event_type = EVENT_TIMER; 154 | timer_event->data.fd = tfd; 155 | // The data.fd and data.ptr is union.... 156 | // tev.data.fd = tfd; 157 | tev.data.ptr = timer_event; 158 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, tfd, &tev) == -1) { 159 | perror("Epoll ctl add failed"); 160 | close(server_fd); 161 | close(epoll_fd); 162 | free(server_event); 163 | server_event = NULL; 164 | exit(EXIT_FAILURE); 165 | } 166 | } 167 | 168 | return epoll_fd; 169 | } 170 | 171 | static void handle_event(int epoll_fd, struct epoll_event *event) { 172 | struct sockaddr_in address; 173 | int addrlen = sizeof(address); 174 | node_data_t *event_data = (node_data_t *)event->data.ptr; 175 | 176 | if (event_data != NULL && event_data->event_type == EVENT_SERVER) { 177 | // Accept a new client connection 178 | int client_socket = 179 | accept(event_data->data.fd, (struct sockaddr *)&address, 180 | (socklen_t *)&addrlen); 181 | if (client_socket < 0) { 182 | perror("Accept failed"); 183 | return; 184 | } 185 | 186 | // Connection accepted at this point 187 | 188 | set_non_blocking(client_socket); 189 | 190 | client_t *client = calloc(1, sizeof(client_t)); 191 | if (!client) { 192 | perror("malloc failed"); 193 | close(client_socket); 194 | return; 195 | } 196 | 197 | client->fd = client_socket; 198 | client->read_buffer_len = 0; 199 | client->write_buffer_len = 0; 200 | client->write_buffer_pos = 0; 201 | client->close_after_write = 0; 202 | 203 | node_data_t *client_event = malloc(sizeof(node_data_t)); 204 | if (!client_event) { 205 | perror("malloc failed"); 206 | close(client_socket); 207 | free(client); 208 | client = NULL; 209 | return; 210 | } 211 | client_event->event_type = EVENT_CLIENT; 212 | client_event->data.client = client; 213 | 214 | // Add the client socket to epoll 215 | struct epoll_event ev; 216 | ev.events = 217 | EPOLLIN | EPOLLET | EPOLLET | EPOLLHUP | EPOLLERR | EPOLLRDHUP; 218 | ev.data.ptr = client_event; 219 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &ev) == -1) { 220 | perror("Epoll ctl add failed"); 221 | close(client_socket); 222 | free(client); 223 | client = NULL; 224 | free(client_event); 225 | client_event = NULL; 226 | return; 227 | } 228 | 229 | increment_active_connections(); 230 | log_debug("Added Client: %p\n", client); 231 | } else if (event_data != NULL && event_data->event_type == EVENT_CLIENT) { 232 | client_t *client = event_data->data.client; 233 | 234 | // Handle client disconnect or error 235 | if (event->events & (EPOLLHUP | EPOLLERR | EPOLLRDHUP)) { 236 | log_debug("Disconnect event for %p\n", event_data->data.client); 237 | delete_resources(epoll_fd, client, event); 238 | return; 239 | } 240 | 241 | // Handle the client request 242 | if (event->events & EPOLLIN) { 243 | handle_client_read(client, event, epoll_fd); 244 | } 245 | // use an if-else clause here if you want more resource fairness and not 246 | // speed on response 247 | if (event->events & EPOLLOUT) { 248 | handle_client_write(client, event, epoll_fd); 249 | } 250 | 251 | } else if (event_data != NULL && event_data->event_type == EVENT_TIMER) { 252 | uint64_t expirations; 253 | ssize_t bytes = read(timer_fd, &expirations, 254 | sizeof(expirations)); // Acknowledge the timer 255 | if (bytes == -1) { 256 | perror("read from timer_fd failed"); 257 | return; 258 | } 259 | hash_table_cleanup_expired(ttl_manager->hash_table_main); 260 | } 261 | } 262 | 263 | void hash_table_cleanup_expired(hash_table_t *ht) { 264 | log_info("CleanUp Triggered\n"); 265 | 266 | time_t current_time = time(NULL); 267 | 268 | for (int i = 0; i < ht->capacity; i++) { 269 | hash_entry_t *entry = ht->table[i]; 270 | while (entry != NULL) { 271 | ttl_entry_t *ttl_entry_node = entry->value; 272 | hash_entry_t *temp = entry->next; 273 | if (is_entry_expired(ttl_entry_node, current_time)) { 274 | log_debug("CleanUp Key: %s\n", entry->key); 275 | hash_table_remove(ttl_manager->hash_table_main, entry->key, 276 | custom_cleanup_ttl); 277 | } 278 | entry = temp; 279 | } 280 | } 281 | 282 | log_info("CleanUp Ended\n"); 283 | } 284 | 285 | void create_ttl_manager(void) { 286 | ttl_manager = malloc(sizeof(ttl_manager_t)); 287 | if (!ttl_manager) { 288 | log_error("failed to allocate memory: %s for ttl manager", 289 | strerror(errno)); 290 | exit(EXIT_FAILURE); 291 | } 292 | ttl_manager->hash_table_main = hash_table_create(HASH_TABLE_STARTING_SIZE); 293 | } 294 | 295 | void create_lru_manager(void) { 296 | lru_manager = malloc(sizeof(lru_manager_t)); 297 | if (!lru_manager) { 298 | log_error("failed to allocate memory: %s for lru manager", 299 | strerror(errno)); 300 | exit(EXIT_FAILURE); 301 | } 302 | lru_manager->hash_table_main = hash_table_create(HASH_TABLE_STARTING_SIZE); 303 | lru_manager->head = NULL; 304 | lru_manager->tail = NULL; 305 | lru_manager->size = 0; 306 | lru_manager->capacity = LRU_CAPACITY; 307 | // disbale the resize functionality of the hashtable since the LRU needs to 308 | // have constant size 309 | hash_table_set_resize_flag(0); 310 | } 311 | 312 | void init_eviction_mode() { 313 | if (EVICTION == EVICTION_TTL) { 314 | log_debug("In EVICTION_TTL mode\n"); 315 | create_ttl_manager(); 316 | 317 | } else if (EVICTION == EVICTION_LRU) { 318 | log_debug("In EVICTION_LRU mode\n"); 319 | create_lru_manager(); 320 | log_debug("LRU capacity: %d", LRU_CAPACITY); 321 | 322 | } else { 323 | log_error("UNKNOWN EVICTION POLICY mode\n"); 324 | } 325 | } 326 | 327 | int run_server(int port) { 328 | // #warning "TESTING macro is defined" 329 | CONNECTIONS_STORE(active_connections, CONNECTIONS_INIT); 330 | KEEP_RUNNING_STORE(keep_running, KEEP_RUNNING_INIT); 331 | // keep_running = 1; 332 | set_server_state(SERVER_STATE_INITIALIZING); 333 | // hash_table_main = hash_table_create(HASH_TABLE_STARTING_SIZE); 334 | init_eviction_mode(); 335 | log_debug("Hashtable starting size: %d", HASH_TABLE_STARTING_SIZE); 336 | 337 | // Set up signal handling 338 | setup_signal_handling(); 339 | int server_fd = setup_server_socket(port); 340 | timer_fd = setup_cleanup_timerfd(); 341 | int epoll_fd = setup_epoll(server_fd, timer_fd); 342 | 343 | log_debug("SERVER STARTED\n"); 344 | log_info("Server is listening on port %d\n", port); 345 | 346 | set_server_state(SERVER_STATE_RUNNING); 347 | // log_debug("lru capacity: %d", lru_manager->capacity); 348 | 349 | struct epoll_event events[MAX_EVENTS]; 350 | 351 | // Event Loop 352 | while (KEEP_RUNNING_LOAD(keep_running)) { 353 | int nfds = 354 | epoll_wait(epoll_fd, events, MAX_EVENTS, 355 | -1); // returns as soon as an event occurs, no delay 356 | if (!KEEP_RUNNING_LOAD(keep_running)) { 357 | break; 358 | } 359 | if (nfds == -1) { 360 | if (errno == EINTR) { 361 | break; 362 | } else { 363 | perror("epoll_wait failed"); 364 | break; 365 | } 366 | } 367 | 368 | for (int i = 0; i < nfds; ++i) { 369 | handle_event(epoll_fd, &events[i]); 370 | } 371 | } 372 | // Cleanup on shutdown 373 | // cleanup_all_clients(&client_list_head); 374 | set_server_state(SERVER_STATE_STOPPED); 375 | free(server_event); 376 | server_event = NULL; 377 | free(timer_event); 378 | timer_event = NULL; 379 | close(epoll_fd); 380 | close(server_fd); 381 | close(timer_fd); 382 | if (EVICTION == EVICTION_TTL) { 383 | hash_table_cleanup(ttl_manager->hash_table_main, custom_cleanup_ttl); 384 | free(ttl_manager); 385 | } else if (EVICTION == EVICTION_LRU) { 386 | hash_table_cleanup(lru_manager->hash_table_main, custom_cleanup_lru); 387 | free(lru_manager); 388 | } 389 | CONNECTIONS_STORE(active_connections, CONNECTIONS_INIT); 390 | // active_connections = CONNECTIONS_INIT; 391 | log_info("SERVER STOPPED\n"); 392 | return 0; 393 | } 394 | -------------------------------------------------------------------------------- /src/signal_handler.c: -------------------------------------------------------------------------------- 1 | #include "../include/signal_handler.h" 2 | 3 | KEEP_RUNNING_TYPE keep_running = KEEP_RUNNING_INIT; 4 | // KEEP_RUNNING_STORE(keep_running, KEEP_RUNNING_INIT); 5 | 6 | void set_event_loop_state(int state) { // 0 for stop, 1 for running 7 | // #warning "TESTING macro is defined" 8 | KEEP_RUNNING_STORE(keep_running, state); 9 | } 10 | 11 | void handle_shutdown_signal(int signal_number) { // 0 for stop, 1 for running 12 | (void)signal_number; 13 | KEEP_RUNNING_STORE(keep_running, 0); 14 | } 15 | 16 | void setup_signal_handling(void) { 17 | struct sigaction sa; 18 | sa.sa_handler = handle_shutdown_signal; 19 | sigemptyset(&sa.sa_mask); 20 | sa.sa_flags = 0; 21 | 22 | sigaction(SIGINT, &sa, NULL); 23 | sigaction(SIGTERM, &sa, NULL); 24 | } 25 | -------------------------------------------------------------------------------- /src/ttl_manager.c: -------------------------------------------------------------------------------- 1 | #include "../include/ttl_manager.h" 2 | 3 | // A custom clean function that is used as a callback in the hashtable 4 | void custom_cleanup_ttl(void *arg) { 5 | ttl_entry_t *ttl_entry = arg; 6 | if (ttl_entry && ttl_entry->value) { 7 | free(ttl_entry->value); 8 | } 9 | } 10 | 11 | bool is_entry_expired(ttl_entry_t *entry, time_t current_time) { 12 | if (entry->ttl == 0) { 13 | log_debug("Not expired\n"); 14 | return false; 15 | } 16 | return difftime(current_time, entry->timestamp) > entry->ttl; 17 | } 18 | 19 | void ttl_set(char *key, char *value, char *ttl_value, char *response) { 20 | char *response_value = NULL; 21 | // Set a key value on hashmap 22 | response_value = "OK"; 23 | log_debug("key: %s\n", key); 24 | log_debug("value: %s\n", value); 25 | log_debug("ttl_value: %s\n", ttl_value); 26 | 27 | ttl_entry_t new_ttl_entry; 28 | new_ttl_entry.value = strdup(value); 29 | if (!new_ttl_entry.value) { 30 | fprintf(stderr, "failed to allocate memory during hash_table_set"); 31 | response_value = "ERROR: MEMORY ALLOC FAILURE"; 32 | write_response_str(response, response_value); 33 | return; 34 | } 35 | new_ttl_entry.timestamp = time(NULL); 36 | 37 | char *endptr; 38 | errno = 0; 39 | long ttl = strtol(ttl_value, &endptr, 10); 40 | if (errno != 0 || endptr == ttl_value || *endptr != '\0' || ttl < 0) { 41 | response_value = "ERROR: INVALID TTL"; 42 | write_response_str(response, response_value); 43 | free(new_ttl_entry.value); 44 | return; 45 | } 46 | 47 | new_ttl_entry.ttl = (int)ttl; 48 | 49 | int error = 50 | hash_table_set(ttl_manager->hash_table_main, key, &new_ttl_entry, 51 | sizeof(new_ttl_entry), custom_cleanup_ttl); 52 | if (error != 0) { 53 | fprintf(stderr, "failed to allocate memory during set_value"); 54 | response_value = "ERROR: MEMORY ALLOC FAILURE"; 55 | } 56 | 57 | write_response_str(response, response_value); 58 | } 59 | 60 | void ttl_get(char *key, char *response) { 61 | char *response_value = NULL; 62 | // Get a value from hashmap 63 | ttl_entry_t *ttl_entry = hash_table_get(ttl_manager->hash_table_main, key); 64 | if (ttl_entry == NULL) { 65 | response_value = "ERROR: KEY NOT FOUND"; 66 | } else { 67 | time_t current_time = time(NULL); 68 | if (is_entry_expired(ttl_entry, current_time)) { 69 | // Expired 70 | hash_table_remove(ttl_manager->hash_table_main, key, 71 | custom_cleanup_ttl); 72 | response_value = "ERROR: KEY NOT FOUND"; 73 | } else { 74 | // Valid 75 | response_value = ttl_entry->value; 76 | } 77 | } 78 | write_response_str(response, response_value); 79 | } 80 | 81 | void ttl_delete(char *key, char *response) { 82 | char *response_value = NULL; 83 | // Delete a value 84 | response_value = "OK"; 85 | ttl_entry_t *ttl_entry = hash_table_get(ttl_manager->hash_table_main, key); 86 | if (ttl_entry == NULL) { 87 | response_value = "ERROR: KEY NOT FOUND"; 88 | } else { 89 | hash_table_remove(ttl_manager->hash_table_main, key, 90 | custom_cleanup_ttl); 91 | } 92 | write_response_str(response, response_value); 93 | } 94 | -------------------------------------------------------------------------------- /test/mock_malloc.c: -------------------------------------------------------------------------------- 1 | #include "../include/mock_malloc.h" 2 | 3 | static int malloc_fail = 0; 4 | 5 | void set_malloc_fail(int fail) { 6 | malloc_fail = fail; 7 | } 8 | 9 | void *mock_malloc(size_t size) { 10 | if (malloc_fail) { 11 | return NULL; 12 | } 13 | return malloc(size); 14 | } 15 | 16 | void *mock_calloc(size_t nmemb, size_t size) { 17 | if (malloc_fail) { 18 | return NULL; 19 | } 20 | return calloc(nmemb, size); 21 | } 22 | -------------------------------------------------------------------------------- /test/test_app.c: -------------------------------------------------------------------------------- 1 | #include "../unity/unity.h" 2 | #include "test_common_cache.h" 3 | #include "util.h" 4 | #if defined(EVICTION_FLAG_TTL) 5 | #include "test_ttl_cache.h" 6 | #endif 7 | #if defined(EVICTION_FLAG_LRU) 8 | #include "test_lru_cache.h" 9 | #endif 10 | 11 | pthread_t server_thread; 12 | 13 | void setUp(void) { 14 | static int port = TEST_PORT; 15 | pthread_create(&server_thread, NULL, run_server_thread, &port); 16 | start_server(); 17 | } 18 | 19 | void tearDown(void) { 20 | shutdown_server(); 21 | pthread_join(server_thread, NULL); 22 | } 23 | 24 | int main(void) { 25 | UNITY_BEGIN(); 26 | // Common verification test cases on all eviction policies 27 | RUN_TEST(test_run_server_initialization); 28 | RUN_TEST(test_run_server_multiple_clients); 29 | RUN_TEST(test_cache_api_error_command_too_large); 30 | RUN_TEST(test_cache_api_error_command_incomplete); 31 | RUN_TEST(test_cache_api_partitioned_msg); 32 | RUN_TEST(test_cache_api_error_unknown_or_malformed_command); 33 | #ifdef EVICTION_FLAG_TTL 34 | RUN_TEST(test_ttl_cache_api); 35 | RUN_TEST(test_passive_ttl_cache); 36 | RUN_TEST(test_active_ttl_cache); 37 | RUN_TEST(test_invalid_ttl_value); 38 | #endif 39 | #ifdef EVICTION_FLAG_LRU 40 | // RUN_TEST(test_lru_cache_set); 41 | RUN_TEST(test_lru_cache_api); 42 | RUN_TEST(test_lru_cache_eviction); 43 | #endif 44 | 45 | return UNITY_END(); 46 | } 47 | -------------------------------------------------------------------------------- /test/test_common_cache.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | void test_run_server_initialization(void) { 4 | char buffer[BUFFER_SIZE]; 5 | 6 | send_client_msg_in_new_conn_and_wait_response( 7 | "SET test_key test_value 30\r\n", TEST_PORT, TEST_ADDRESS, buffer); 8 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 9 | } 10 | 11 | void test_run_server_multiple_clients(void) { 12 | char buffer[BUFFER_SIZE]; 13 | 14 | int sockfd1 = connect_client(TEST_PORT, TEST_ADDRESS); 15 | int sockfd2 = connect_client(TEST_PORT, TEST_ADDRESS); 16 | int sockfd3 = connect_client(TEST_PORT, TEST_ADDRESS); 17 | // Verify that the num of clients is 3 using some text-based command 18 | send_client_msg_and_wait_response(sockfd2, "CONNECTIONS\r\n", buffer); 19 | TEST_ASSERT_EQUAL_STRING("3\r\n", buffer); 20 | 21 | disconnect_client(sockfd2); 22 | // Verify that the num of clients is 2 using some text-based command 23 | send_client_msg_and_wait_response(sockfd3, "CONNECTIONS\r\n", buffer); 24 | TEST_ASSERT_EQUAL_STRING("2\r\n", buffer); 25 | 26 | disconnect_client(sockfd3); 27 | disconnect_client(sockfd1); 28 | } 29 | 30 | void test_cache_api_error_command_incomplete(void) { 31 | char buffer[BUFFER_SIZE]; 32 | 33 | // Test scenario 34 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 35 | 36 | // Test SET command 37 | send_client_msg_and_wait_response( 38 | sockfd, "SET test_key1 a_very_long_test_valuva 30", buffer); 39 | TEST_ASSERT_EQUAL_STRING("ERROR: Command incomplete\r\n", buffer); 40 | 41 | disconnect_client(sockfd); 42 | } 43 | 44 | void test_cache_api_error_command_too_large(void) { 45 | char buffer[BUFFER_SIZE]; 46 | 47 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 48 | 49 | // Test SET command 50 | send_client_msg_and_wait_response( 51 | sockfd, 52 | "SET test_key1 a_very_long_test_valuea_very_long_test_val 30\r\n", 53 | buffer); 54 | TEST_ASSERT_EQUAL_STRING("ERROR: Command too large\r\n", buffer); 55 | 56 | disconnect_client(sockfd); 57 | } 58 | 59 | void test_cache_api_error_unknown_or_malformed_command(void) { 60 | char buffer[BUFFER_SIZE]; 61 | 62 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 63 | 64 | send_client_msg_and_wait_response(sockfd, "PET test_key1 a_val 30\r\n", 65 | buffer); 66 | TEST_ASSERT_EQUAL_STRING("ERROR: UNKNOWN OR MALFORMED COMMAND\r\n", buffer); 67 | 68 | disconnect_client(sockfd); 69 | } 70 | 71 | void test_cache_api_partitioned_msg(void) { 72 | char buffer[BUFFER_SIZE]; 73 | 74 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 75 | 76 | // Test SET command 77 | send_client_msg(sockfd, "SET test_key1 a_very_lon"); 78 | send_client_msg_and_wait_response(sockfd, "g_key 30\r\n", buffer); 79 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 80 | 81 | disconnect_client(sockfd); 82 | } 83 | -------------------------------------------------------------------------------- /test/test_common_cache.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_COMMON_H 2 | #define TEST_COMMON_H 3 | void test_cache_api_partitioned_msg(void); 4 | void test_cache_api_error_unknown_or_malformed_command(void); 5 | void test_cache_api_error_command_too_large(void); 6 | void test_cache_api_error_command_incomplete(void); 7 | void test_run_server_multiple_clients(void); 8 | void test_run_server_initialization(void); 9 | #endif 10 | 11 | -------------------------------------------------------------------------------- /test/test_lru_cache.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | // Test variables 4 | // char test_output_buffer[1024]; 5 | // Helper functions 6 | // void mock_response_writer(char *response, char *response_value) { 7 | // (void)response; 8 | // snprintf(test_output_buffer, sizeof(test_output_buffer), "%s", 9 | // response_value); 10 | // } 11 | 12 | // Test cases 13 | 14 | // Unit test 15 | // void test_lru_cache_set(void) { 16 | // lru_manager = malloc(sizeof(lru_manager_t)); 17 | // if (!lru_manager) { 18 | // log_error("failed to allocate memory: %s for lru manager", 19 | // strerror(errno)); 20 | // exit(EXIT_FAILURE); 21 | // } 22 | // lru_manager->hash_table_main = 23 | // hash_table_create(HASH_TABLE_STARTING_SIZE); lru_manager->head = NULL; 24 | // lru_manager->tail = NULL; 25 | // lru_manager->size = 0; 26 | // lru_manager->capacity = LRU_CAPACITY; 27 | // set_response_writer(mock_response_writer); 28 | // 29 | // lru_set("key1", "value1", NULL); 30 | // TEST_ASSERT_EQUAL_STRING("OK", test_output_buffer); 31 | // 32 | // set_response_writer(write_response_str); 33 | // hash_table_cleanup(lru_manager->hash_table_main, custom_cleanup_lru); 34 | // free(lru_manager); 35 | // } 36 | 37 | void test_lru_cache_api(void) { 38 | char buffer[BUFFER_SIZE]; 39 | 40 | // Test scenario 41 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 42 | 43 | // Test SET command 44 | send_client_msg_and_wait_response(sockfd, "SET test_key test_value\r\n", 45 | buffer); 46 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 47 | // re - set the same key 48 | send_client_msg_and_wait_response(sockfd, "SET test_key test_value\r\n", 49 | buffer); 50 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 51 | 52 | // Test SET command 53 | send_client_msg_and_wait_response(sockfd, "SET test_key1 test_value1\r\n", 54 | buffer); 55 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 56 | 57 | // Test GET command 58 | send_client_msg_and_wait_response(sockfd, "GET test_key\r\n", buffer); 59 | TEST_ASSERT_EQUAL_STRING("test_value\r\n", buffer); 60 | 61 | // Test GET command error 62 | send_client_msg_and_wait_response(sockfd, "GET test_invalid_key\r\n", 63 | buffer); 64 | TEST_ASSERT_EQUAL_STRING("ERROR: KEY NOT FOUND\r\n", buffer); 65 | 66 | // Test GET command 67 | send_client_msg_and_wait_response(sockfd, "GET test_key1\r\n", buffer); 68 | TEST_ASSERT_EQUAL_STRING("test_value1\r\n", buffer); 69 | 70 | // Test GET command 71 | send_client_msg_and_wait_response(sockfd, "DELETE test_key1\r\n", buffer); 72 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 73 | 74 | // Test GET command error 75 | send_client_msg_and_wait_response(sockfd, "GET test_key1\r\n", buffer); 76 | TEST_ASSERT_EQUAL_STRING("ERROR: KEY NOT FOUND\r\n", buffer); 77 | 78 | // Test CONNECTIONS command 79 | send_client_msg_and_wait_response(sockfd, "CONNECTIONS\r\n", buffer); 80 | TEST_ASSERT_EQUAL_STRING("1\r\n", buffer); 81 | 82 | // Test KEYS_NUM command 83 | send_client_msg_and_wait_response(sockfd, "KEYS_NUM\r\n", buffer); 84 | TEST_ASSERT_EQUAL_STRING("1\r\n", buffer); 85 | 86 | disconnect_client(sockfd); 87 | } 88 | 89 | void test_lru_cache_eviction(void) { 90 | char buffer[BUFFER_SIZE]; 91 | 92 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 93 | 94 | // Set 5 values, the max capacity for testing 95 | send_client_msg_and_wait_response(sockfd, "SET test_key1 test_value1\r\n", 96 | buffer); 97 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 98 | 99 | send_client_msg_and_wait_response(sockfd, "SET test_key2 test_value2\r\n", 100 | buffer); 101 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 102 | send_client_msg_and_wait_response(sockfd, "SET test_key3 test_value3\r\n", 103 | buffer); 104 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 105 | send_client_msg_and_wait_response(sockfd, "SET test_key4 test_value4\r\n", 106 | buffer); 107 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 108 | send_client_msg_and_wait_response(sockfd, "SET test_key5 test_value5\r\n", 109 | buffer); 110 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 111 | 112 | send_client_msg_and_wait_response(sockfd, "GET test_key1\r\n", buffer); 113 | TEST_ASSERT_EQUAL_STRING("test_value1\r\n", buffer); 114 | send_client_msg_and_wait_response(sockfd, "GET test_key2\r\n", buffer); 115 | TEST_ASSERT_EQUAL_STRING("test_value2\r\n", buffer); 116 | send_client_msg_and_wait_response(sockfd, "GET test_key3\r\n", buffer); 117 | TEST_ASSERT_EQUAL_STRING("test_value3\r\n", buffer); 118 | send_client_msg_and_wait_response(sockfd, "GET test_key4\r\n", buffer); 119 | TEST_ASSERT_EQUAL_STRING("test_value4\r\n", buffer); 120 | send_client_msg_and_wait_response(sockfd, "GET test_key5\r\n", buffer); 121 | TEST_ASSERT_EQUAL_STRING("test_value5\r\n", buffer); 122 | 123 | // Change a value of an existing key 124 | send_client_msg_and_wait_response(sockfd, "SET test_key1 test_value01\r\n", 125 | buffer); 126 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 127 | send_client_msg_and_wait_response(sockfd, "GET test_key1\r\n", buffer); 128 | TEST_ASSERT_EQUAL_STRING("test_value01\r\n", buffer); 129 | 130 | // Add a new key, the test_key2 key should be at the end of the queue 131 | // therefore be deleted 132 | send_client_msg_and_wait_response(sockfd, "SET test_key6 test_value6\r\n", 133 | buffer); 134 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 135 | send_client_msg_and_wait_response(sockfd, "GET test_key6\r\n", buffer); 136 | TEST_ASSERT_EQUAL_STRING("test_value6\r\n", buffer); 137 | 138 | send_client_msg_and_wait_response(sockfd, "GET test_key2\r\n", buffer); 139 | TEST_ASSERT_EQUAL_STRING("ERROR: KEY NOT FOUND\r\n", buffer); 140 | 141 | send_client_msg_and_wait_response(sockfd, "DELETE test_key1\r\n", buffer); 142 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 143 | 144 | send_client_msg_and_wait_response(sockfd, "GET test_key1\r\n", buffer); 145 | TEST_ASSERT_EQUAL_STRING("ERROR: KEY NOT FOUND\r\n", buffer); 146 | 147 | send_client_msg_and_wait_response(sockfd, "SET test_key7 test_value1\r\n", 148 | buffer); 149 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 150 | send_client_msg_and_wait_response(sockfd, "GET test_key7\r\n", buffer); 151 | TEST_ASSERT_EQUAL_STRING("test_value1\r\n", buffer); 152 | 153 | send_client_msg_and_wait_response(sockfd, "GET test_key3\r\n", buffer); 154 | TEST_ASSERT_EQUAL_STRING("test_value3\r\n", buffer); 155 | 156 | send_client_msg_and_wait_response(sockfd, "GET test_key6\r\n", buffer); 157 | TEST_ASSERT_EQUAL_STRING("test_value6\r\n", buffer); 158 | 159 | send_client_msg_and_wait_response(sockfd, "KEYS_NUM\r\n", buffer); 160 | TEST_ASSERT_EQUAL_STRING("5\r\n", buffer); 161 | 162 | disconnect_client(sockfd); 163 | } 164 | -------------------------------------------------------------------------------- /test/test_lru_cache.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_LRU_H 2 | #define TEST_LRU_H 3 | // lru section 4 | //void test_lru_cache_set(void); 5 | void test_lru_cache_api(void); 6 | void test_lru_cache_eviction(void); 7 | #endif 8 | 9 | -------------------------------------------------------------------------------- /test/test_ttl_cache.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | // Test cases 4 | 5 | void test_ttl_cache_api(void) { 6 | char buffer[BUFFER_SIZE]; 7 | 8 | // Test scenario 9 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 10 | 11 | // Test SET command 12 | send_client_msg_and_wait_response(sockfd, "SET test_key test_value 30\r\n", 13 | buffer); 14 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 15 | // re-set the same key 16 | send_client_msg_and_wait_response(sockfd, "SET test_key test_value 30\r\n", 17 | buffer); 18 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 19 | 20 | // Test SET command 21 | send_client_msg_and_wait_response( 22 | sockfd, "SET test_key1 test_value1 30\r\n", buffer); 23 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 24 | 25 | // Test GET command 26 | send_client_msg_and_wait_response(sockfd, "GET test_key\r\n", buffer); 27 | TEST_ASSERT_EQUAL_STRING("test_value\r\n", buffer); 28 | 29 | // Test GET command error 30 | send_client_msg_and_wait_response(sockfd, "GET test_invalid_key\r\n", 31 | buffer); 32 | TEST_ASSERT_EQUAL_STRING("ERROR: KEY NOT FOUND\r\n", buffer); 33 | 34 | // Test GET command 35 | send_client_msg_and_wait_response(sockfd, "GET test_key1\r\n", buffer); 36 | TEST_ASSERT_EQUAL_STRING("test_value1\r\n", buffer); 37 | 38 | // Test GET command 39 | send_client_msg_and_wait_response(sockfd, "DELETE test_key1\r\n", buffer); 40 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 41 | 42 | // Test GET command error 43 | send_client_msg_and_wait_response(sockfd, "GET test_key1\r\n", buffer); 44 | TEST_ASSERT_EQUAL_STRING("ERROR: KEY NOT FOUND\r\n", buffer); 45 | 46 | // Test CONNECTIONS command 47 | send_client_msg_and_wait_response(sockfd, "CONNECTIONS\r\n", buffer); 48 | TEST_ASSERT_EQUAL_STRING("1\r\n", buffer); 49 | 50 | // Test KEYS_NUM command 51 | send_client_msg_and_wait_response(sockfd, "KEYS_NUM\r\n", buffer); 52 | TEST_ASSERT_EQUAL_STRING("1\r\n", buffer); 53 | 54 | disconnect_client(sockfd); 55 | } 56 | 57 | void test_invalid_ttl_value(void) { 58 | char buffer[BUFFER_SIZE]; 59 | 60 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 61 | 62 | send_client_msg_and_wait_response( 63 | sockfd, "SET test_key11 test_value -1\r\n", buffer); 64 | TEST_ASSERT_EQUAL_STRING("ERROR: INVALID TTL\r\n", buffer); 65 | 66 | send_client_msg_and_wait_response( 67 | sockfd, "SET test_key11 test_value aa\r\n", buffer); 68 | TEST_ASSERT_EQUAL_STRING("ERROR: INVALID TTL\r\n", buffer); 69 | 70 | disconnect_client(sockfd); 71 | } 72 | 73 | void test_passive_ttl_cache(void) { 74 | char buffer[BUFFER_SIZE]; 75 | 76 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 77 | 78 | send_client_msg_and_wait_response(sockfd, "SET test_key11 test_value 1\r\n", 79 | buffer); 80 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 81 | send_client_msg_and_wait_response(sockfd, "GET test_key11\r\n", buffer); 82 | TEST_ASSERT_EQUAL_STRING("test_value\r\n", buffer); 83 | send_client_msg_and_wait_response(sockfd, "KEYS_NUM\r\n", buffer); 84 | TEST_ASSERT_EQUAL_STRING("1\r\n", buffer); 85 | 86 | // Should not expire 87 | send_client_msg_and_wait_response( 88 | sockfd, "SET test_key_no_expire no_expire 0\r\n", buffer); 89 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 90 | send_client_msg_and_wait_response(sockfd, "GET test_key_no_expire\r\n", 91 | buffer); 92 | TEST_ASSERT_EQUAL_STRING("no_expire\r\n", buffer); 93 | 94 | // Test GET command 95 | sleep(2); 96 | send_client_msg_and_wait_response(sockfd, "GET test_key11\r\n", buffer); 97 | TEST_ASSERT_EQUAL_STRING("ERROR: KEY NOT FOUND\r\n", buffer); 98 | 99 | send_client_msg_and_wait_response(sockfd, "GET test_key_no_expire\r\n", 100 | buffer); 101 | TEST_ASSERT_EQUAL_STRING("no_expire\r\n", buffer); 102 | 103 | send_client_msg_and_wait_response(sockfd, "KEYS_NUM\r\n", buffer); 104 | TEST_ASSERT_EQUAL_STRING("1\r\n", buffer); 105 | 106 | disconnect_client(sockfd); 107 | } 108 | 109 | void test_active_ttl_cache(void) { 110 | char buffer[BUFFER_SIZE]; 111 | 112 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 113 | 114 | send_client_msg_and_wait_response(sockfd, "SET test_key11 test_value 1\r\n", 115 | buffer); 116 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 117 | send_client_msg_and_wait_response(sockfd, "GET test_key11\r\n", buffer); 118 | TEST_ASSERT_EQUAL_STRING("test_value\r\n", buffer); 119 | 120 | // Should not expire 121 | send_client_msg_and_wait_response( 122 | sockfd, "SET test_key_no_expire no_expire 0\r\n", buffer); 123 | TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 124 | send_client_msg_and_wait_response(sockfd, "GET test_key_no_expire\r\n", 125 | buffer); 126 | TEST_ASSERT_EQUAL_STRING("no_expire\r\n", buffer); 127 | 128 | send_client_msg_and_wait_response(sockfd, "KEYS_NUM\r\n", buffer); 129 | TEST_ASSERT_EQUAL_STRING("2\r\n", buffer); 130 | 131 | // Test GET command 132 | sleep(4); 133 | send_client_msg_and_wait_response(sockfd, "GET test_key_no_expire\r\n", 134 | buffer); 135 | TEST_ASSERT_EQUAL_STRING("no_expire\r\n", buffer); 136 | 137 | send_client_msg_and_wait_response(sockfd, "KEYS_NUM\r\n", buffer); 138 | TEST_ASSERT_EQUAL_STRING("1\r\n", buffer); 139 | 140 | disconnect_client(sockfd); 141 | } 142 | 143 | void test_ttl_cache_set(void) { 144 | char buffer[BUFFER_SIZE]; 145 | 146 | int sockfd = connect_client(TEST_PORT, TEST_ADDRESS); 147 | 148 | send_client_msg_and_wait_response( 149 | sockfd, "SET test_key11 test_value -1\r\n", buffer); 150 | TEST_ASSERT_EQUAL_STRING("ERROR: INVALID TTL\r\n", buffer); 151 | 152 | send_client_msg_and_wait_response( 153 | sockfd, "SET test_key11 test_value aa\r\n", buffer); 154 | TEST_ASSERT_EQUAL_STRING("ERROR: INVALID TTL\r\n", buffer); 155 | 156 | disconnect_client(sockfd); 157 | } 158 | -------------------------------------------------------------------------------- /test/test_ttl_cache.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_TTL_H 2 | #define TEST_TTL_H 3 | // ttl section 4 | void test_active_ttl_cache(void); 5 | void test_passive_ttl_cache(void); 6 | void test_invalid_ttl_value(void); 7 | void test_ttl_cache_api(void); 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /test/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | // vars 3 | const unsigned int WAITING_SERVER_TIMEOUT = 5 * 1000 * 1000; // 5 sec 4 | const unsigned int WAITING_SERVER_INIT = 5 * 1000 * 1000; // 5 sec 5 | // 6 | char *TEST_ADDRESS = "127.0.0.1"; 7 | 8 | // A small delay for server to start and avoid connection refusals 9 | // void server_delay(unsigned int delay) { usleep(delay); } 10 | 11 | // Helper funtions 12 | void print_msg(char *msg) { fprintf(stderr, "%s", msg); } 13 | 14 | void wait_for_no_active_connections(unsigned int waiting_time) { 15 | unsigned int total_time = 0; 16 | const unsigned int delay = 100000; // 100ms 17 | // atomic_load(&(var)) 18 | while (get_active_connections() > 0) { 19 | usleep(delay); 20 | total_time += delay; 21 | if (total_time > waiting_time) { 22 | print_msg( 23 | "Timeout waiting for server to close all the connections\n"); 24 | exit(EXIT_FAILURE); 25 | } 26 | } 27 | } 28 | 29 | void shutdown_server(void) { 30 | wait_for_no_active_connections(WAITING_SERVER_TIMEOUT); 31 | // close the event loop 32 | set_event_loop_state(0); 33 | // print_msg("send disconnect signal\n"); 34 | // this is needed so the event loop get in state of checking again if it is 35 | // running 36 | connect_disconnect_client(TEST_PORT, TEST_ADDRESS); 37 | } 38 | 39 | void wait_for_server_to_start(unsigned int waiting_time) { 40 | unsigned int total_time = 0; 41 | const unsigned int delay = 100000; // 100ms 42 | while (get_server_state() != SERVER_STATE_RUNNING) { 43 | usleep(delay); 44 | total_time += delay; 45 | if (total_time > waiting_time) { 46 | print_msg("Timeout waiting for server to start running\n"); 47 | exit(EXIT_FAILURE); 48 | } 49 | } 50 | } 51 | 52 | void start_server(void) { wait_for_server_to_start(WAITING_SERVER_INIT); } 53 | 54 | void *run_server_thread(void *arg) { 55 | int *port = (int *)arg; 56 | run_server(*port); 57 | // run_server(*port, 3, 1); 58 | return NULL; 59 | } 60 | 61 | int connect_client(int port, char *ip) { 62 | int sockfd; 63 | struct sockaddr_in server_addr; 64 | // char buffer[BUFFER_SIZE]; 65 | 66 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 67 | if (sockfd < 0) { 68 | print_msg("Socket creation failed"); 69 | exit(EXIT_FAILURE); 70 | } 71 | 72 | server_addr.sin_family = AF_INET; 73 | server_addr.sin_port = htons(port); 74 | if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) { 75 | print_msg("Invalid address"); 76 | close(sockfd); 77 | exit(EXIT_FAILURE); 78 | } 79 | 80 | if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 81 | 0) { 82 | print_msg("Connection to the server failed"); 83 | close(sockfd); 84 | // exit(EXIT_FAILURE); 85 | } 86 | 87 | return sockfd; 88 | } 89 | 90 | void disconnect_client(int sockfd) { close(sockfd); } 91 | 92 | void connect_disconnect_client(int port, char *ip) { 93 | int sockfd = connect_client(port, ip); 94 | // connect_client(port, ip); 95 | disconnect_client(sockfd); 96 | } 97 | 98 | void send_client_msg_and_wait_response(int sockfd, char *msg, char *buffer) { 99 | // char buffer[BUFFER_SIZE]; 100 | 101 | if (send(sockfd, msg, strlen(msg), 0) < 0) { 102 | print_msg("Send failed"); 103 | close(sockfd); 104 | exit(EXIT_FAILURE); 105 | } 106 | 107 | int bytes_received = read(sockfd, buffer, BUFFER_SIZE); 108 | if (bytes_received < 0) { 109 | print_msg("Receive failed"); 110 | close(sockfd); 111 | exit(EXIT_FAILURE); 112 | } 113 | buffer[bytes_received] = '\0'; 114 | } 115 | void send_client_msg(int sockfd, char *msg) { 116 | // char buffer[BUFFER_SIZE]; 117 | 118 | if (send(sockfd, msg, strlen(msg), 0) < 0) { 119 | print_msg("Send failed"); 120 | close(sockfd); 121 | exit(EXIT_FAILURE); 122 | } 123 | } 124 | void send_client_msg_in_new_conn_and_wait_response(char *msg, int port, 125 | char *ip, char *buffer) { 126 | // char buffer[BUFFER_SIZE]; 127 | int sockfd = connect_client(port, ip); 128 | 129 | if (send(sockfd, msg, strlen(msg), 0) < 0) { 130 | print_msg("Send failed"); 131 | close(sockfd); 132 | exit(EXIT_FAILURE); 133 | } 134 | 135 | int bytes_received = read(sockfd, buffer, BUFFER_SIZE); 136 | if (bytes_received < 0) { 137 | print_msg("Receive failed"); 138 | close(sockfd); 139 | exit(EXIT_FAILURE); 140 | } 141 | buffer[bytes_received] = '\0'; 142 | // printf("Msg rcvd: %s", buffer); 143 | // TEST_ASSERT_EQUAL_STRING("OK\r\n", buffer); 144 | 145 | disconnect_client(sockfd); 146 | // close(sockfd); 147 | } 148 | -------------------------------------------------------------------------------- /test/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../include/connection.h" 8 | #include "../include/server.h" 9 | #include "../include/signal_handler.h" 10 | #include "../unity/unity.h" 11 | 12 | 13 | 14 | #define TEST_PORT 8080 15 | 16 | void connect_disconnect_client(int port, char *ip); 17 | void print_msg(char *msg); 18 | void wait_for_no_active_connections(unsigned int waiting_time); 19 | void shutdown_server(void); 20 | void wait_for_server_to_start(unsigned int waiting_time); 21 | void start_server(void); 22 | void *run_server_thread(void *arg); 23 | int connect_client(int port, char *ip); 24 | void disconnect_client(int sockfd); 25 | void connect_disconnect_client(int port, char *ip); 26 | void send_client_msg_and_wait_response(int sockfd, char *msg, char *buffer); 27 | void send_client_msg(int sockfd, char *msg); 28 | void send_client_msg_in_new_conn_and_wait_response(char *msg, int port, 29 | char *ip, char *buffer); 30 | 31 | extern const unsigned int WAITING_SERVER_TIMEOUT; 32 | extern const unsigned int WAITING_SERVER_INIT; 33 | extern char *TEST_ADDRESS; 34 | 35 | #endif // UTIL_H 36 | -------------------------------------------------------------------------------- /unity/unity.c: -------------------------------------------------------------------------------- 1 | /* ========================================================================= 2 | Unity - A Test Framework for C 3 | ThrowTheSwitch.org 4 | Copyright (c) 2007-24 Mike Karlesky, Mark VanderVoord, & Greg Williams 5 | SPDX-License-Identifier: MIT 6 | ========================================================================= */ 7 | 8 | #include "unity.h" 9 | 10 | #ifndef UNITY_PROGMEM 11 | #define UNITY_PROGMEM 12 | #endif 13 | 14 | /* If omitted from header, declare overrideable prototypes here so they're ready for use */ 15 | #ifdef UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION 16 | void UNITY_OUTPUT_CHAR(int); 17 | #endif 18 | 19 | /* Helpful macros for us to use here in Assert functions */ 20 | #define UNITY_FAIL_AND_BAIL do { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) 21 | #define UNITY_IGNORE_AND_BAIL do { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) 22 | #define RETURN_IF_FAIL_OR_IGNORE do { if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) { TEST_ABORT(); } } while (0) 23 | 24 | struct UNITY_STORAGE_T Unity; 25 | 26 | #ifdef UNITY_OUTPUT_COLOR 27 | const char UNITY_PROGMEM UnityStrOk[] = "\033[42mOK\033[0m"; 28 | const char UNITY_PROGMEM UnityStrPass[] = "\033[42mPASS\033[0m"; 29 | const char UNITY_PROGMEM UnityStrFail[] = "\033[41mFAIL\033[0m"; 30 | const char UNITY_PROGMEM UnityStrIgnore[] = "\033[43mIGNORE\033[0m"; 31 | #else 32 | const char UNITY_PROGMEM UnityStrOk[] = "OK"; 33 | const char UNITY_PROGMEM UnityStrPass[] = "PASS"; 34 | const char UNITY_PROGMEM UnityStrFail[] = "FAIL"; 35 | const char UNITY_PROGMEM UnityStrIgnore[] = "IGNORE"; 36 | #endif 37 | static const char UNITY_PROGMEM UnityStrNull[] = "NULL"; 38 | static const char UNITY_PROGMEM UnityStrSpacer[] = ". "; 39 | static const char UNITY_PROGMEM UnityStrExpected[] = " Expected "; 40 | static const char UNITY_PROGMEM UnityStrWas[] = " Was "; 41 | static const char UNITY_PROGMEM UnityStrGt[] = " to be greater than "; 42 | static const char UNITY_PROGMEM UnityStrLt[] = " to be less than "; 43 | static const char UNITY_PROGMEM UnityStrOrEqual[] = "or equal to "; 44 | static const char UNITY_PROGMEM UnityStrNotEqual[] = " to be not equal to "; 45 | static const char UNITY_PROGMEM UnityStrElement[] = " Element "; 46 | static const char UNITY_PROGMEM UnityStrByte[] = " Byte "; 47 | static const char UNITY_PROGMEM UnityStrMemory[] = " Memory Mismatch."; 48 | static const char UNITY_PROGMEM UnityStrDelta[] = " Values Not Within Delta "; 49 | static const char UNITY_PROGMEM UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; 50 | static const char UNITY_PROGMEM UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; 51 | static const char UNITY_PROGMEM UnityStrNullPointerForActual[] = " Actual pointer was NULL"; 52 | #ifndef UNITY_EXCLUDE_FLOAT 53 | static const char UNITY_PROGMEM UnityStrNot[] = "Not "; 54 | static const char UNITY_PROGMEM UnityStrInf[] = "Infinity"; 55 | static const char UNITY_PROGMEM UnityStrNegInf[] = "Negative Infinity"; 56 | static const char UNITY_PROGMEM UnityStrNaN[] = "NaN"; 57 | static const char UNITY_PROGMEM UnityStrDet[] = "Determinate"; 58 | static const char UNITY_PROGMEM UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; 59 | #endif 60 | const char UNITY_PROGMEM UnityStrErrShorthand[] = "Unity Shorthand Support Disabled"; 61 | const char UNITY_PROGMEM UnityStrErrFloat[] = "Unity Floating Point Disabled"; 62 | const char UNITY_PROGMEM UnityStrErrDouble[] = "Unity Double Precision Disabled"; 63 | const char UNITY_PROGMEM UnityStrErr64[] = "Unity 64-bit Support Disabled"; 64 | static const char UNITY_PROGMEM UnityStrBreaker[] = "-----------------------"; 65 | static const char UNITY_PROGMEM UnityStrResultsTests[] = " Tests "; 66 | static const char UNITY_PROGMEM UnityStrResultsFailures[] = " Failures "; 67 | static const char UNITY_PROGMEM UnityStrResultsIgnored[] = " Ignored "; 68 | #ifndef UNITY_EXCLUDE_DETAILS 69 | static const char UNITY_PROGMEM UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; 70 | static const char UNITY_PROGMEM UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; 71 | #endif 72 | /*----------------------------------------------- 73 | * Pretty Printers & Test Result Output Handlers 74 | *-----------------------------------------------*/ 75 | 76 | /*-----------------------------------------------*/ 77 | /* Local helper function to print characters. */ 78 | static void UnityPrintChar(const char* pch) 79 | { 80 | /* printable characters plus CR & LF are printed */ 81 | if ((*pch <= 126) && (*pch >= 32)) 82 | { 83 | UNITY_OUTPUT_CHAR(*pch); 84 | } 85 | /* write escaped carriage returns */ 86 | else if (*pch == 13) 87 | { 88 | UNITY_OUTPUT_CHAR('\\'); 89 | UNITY_OUTPUT_CHAR('r'); 90 | } 91 | /* write escaped line feeds */ 92 | else if (*pch == 10) 93 | { 94 | UNITY_OUTPUT_CHAR('\\'); 95 | UNITY_OUTPUT_CHAR('n'); 96 | } 97 | /* unprintable characters are shown as codes */ 98 | else 99 | { 100 | UNITY_OUTPUT_CHAR('\\'); 101 | UNITY_OUTPUT_CHAR('x'); 102 | UnityPrintNumberHex((UNITY_UINT)*pch, 2); 103 | } 104 | } 105 | 106 | /*-----------------------------------------------*/ 107 | /* Local helper function to print ANSI escape strings e.g. "\033[42m". */ 108 | #ifdef UNITY_OUTPUT_COLOR 109 | static UNITY_UINT UnityPrintAnsiEscapeString(const char* string) 110 | { 111 | const char* pch = string; 112 | UNITY_UINT count = 0; 113 | 114 | while (*pch && (*pch != 'm')) 115 | { 116 | UNITY_OUTPUT_CHAR(*pch); 117 | pch++; 118 | count++; 119 | } 120 | UNITY_OUTPUT_CHAR('m'); 121 | count++; 122 | 123 | return count; 124 | } 125 | #endif 126 | 127 | /*-----------------------------------------------*/ 128 | void UnityPrint(const char* string) 129 | { 130 | const char* pch = string; 131 | 132 | if (pch != NULL) 133 | { 134 | while (*pch) 135 | { 136 | #ifdef UNITY_OUTPUT_COLOR 137 | /* print ANSI escape code */ 138 | if ((*pch == 27) && (*(pch + 1) == '[')) 139 | { 140 | pch += UnityPrintAnsiEscapeString(pch); 141 | continue; 142 | } 143 | #endif 144 | UnityPrintChar(pch); 145 | pch++; 146 | } 147 | } 148 | } 149 | /*-----------------------------------------------*/ 150 | void UnityPrintLen(const char* string, const UNITY_UINT32 length) 151 | { 152 | const char* pch = string; 153 | 154 | if (pch != NULL) 155 | { 156 | while (*pch && ((UNITY_UINT32)(pch - string) < length)) 157 | { 158 | /* printable characters plus CR & LF are printed */ 159 | if ((*pch <= 126) && (*pch >= 32)) 160 | { 161 | UNITY_OUTPUT_CHAR(*pch); 162 | } 163 | /* write escaped carriage returns */ 164 | else if (*pch == 13) 165 | { 166 | UNITY_OUTPUT_CHAR('\\'); 167 | UNITY_OUTPUT_CHAR('r'); 168 | } 169 | /* write escaped line feeds */ 170 | else if (*pch == 10) 171 | { 172 | UNITY_OUTPUT_CHAR('\\'); 173 | UNITY_OUTPUT_CHAR('n'); 174 | } 175 | /* unprintable characters are shown as codes */ 176 | else 177 | { 178 | UNITY_OUTPUT_CHAR('\\'); 179 | UNITY_OUTPUT_CHAR('x'); 180 | UnityPrintNumberHex((UNITY_UINT)*pch, 2); 181 | } 182 | pch++; 183 | } 184 | } 185 | } 186 | 187 | /*-----------------------------------------------*/ 188 | void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style) 189 | { 190 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 191 | { 192 | if (style == UNITY_DISPLAY_STYLE_CHAR) 193 | { 194 | /* printable characters plus CR & LF are printed */ 195 | UNITY_OUTPUT_CHAR('\''); 196 | if ((number <= 126) && (number >= 32)) 197 | { 198 | UNITY_OUTPUT_CHAR((int)number); 199 | } 200 | /* write escaped carriage returns */ 201 | else if (number == 13) 202 | { 203 | UNITY_OUTPUT_CHAR('\\'); 204 | UNITY_OUTPUT_CHAR('r'); 205 | } 206 | /* write escaped line feeds */ 207 | else if (number == 10) 208 | { 209 | UNITY_OUTPUT_CHAR('\\'); 210 | UNITY_OUTPUT_CHAR('n'); 211 | } 212 | /* unprintable characters are shown as codes */ 213 | else 214 | { 215 | UNITY_OUTPUT_CHAR('\\'); 216 | UNITY_OUTPUT_CHAR('x'); 217 | UnityPrintNumberHex((UNITY_UINT)number, 2); 218 | } 219 | UNITY_OUTPUT_CHAR('\''); 220 | } 221 | else 222 | { 223 | UnityPrintNumber(number); 224 | } 225 | } 226 | else if ((style & UNITY_DISPLAY_RANGE_UINT) == UNITY_DISPLAY_RANGE_UINT) 227 | { 228 | UnityPrintNumberUnsigned((UNITY_UINT)number); 229 | } 230 | else 231 | { 232 | UNITY_OUTPUT_CHAR('0'); 233 | UNITY_OUTPUT_CHAR('x'); 234 | UnityPrintNumberHex((UNITY_UINT)number, (char)((style & 0xF) * 2)); 235 | } 236 | } 237 | 238 | /*-----------------------------------------------*/ 239 | void UnityPrintNumber(const UNITY_INT number_to_print) 240 | { 241 | UNITY_UINT number = (UNITY_UINT)number_to_print; 242 | 243 | if (number_to_print < 0) 244 | { 245 | /* A negative number, including MIN negative */ 246 | UNITY_OUTPUT_CHAR('-'); 247 | number = (~number) + 1; 248 | } 249 | UnityPrintNumberUnsigned(number); 250 | } 251 | 252 | /*----------------------------------------------- 253 | * basically do an itoa using as little ram as possible */ 254 | void UnityPrintNumberUnsigned(const UNITY_UINT number) 255 | { 256 | UNITY_UINT divisor = 1; 257 | 258 | /* figure out initial divisor */ 259 | while (number / divisor > 9) 260 | { 261 | divisor *= 10; 262 | } 263 | 264 | /* now mod and print, then divide divisor */ 265 | do 266 | { 267 | UNITY_OUTPUT_CHAR((char)('0' + (number / divisor % 10))); 268 | divisor /= 10; 269 | } while (divisor > 0); 270 | } 271 | 272 | /*-----------------------------------------------*/ 273 | void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print) 274 | { 275 | int nibble; 276 | char nibbles = nibbles_to_print; 277 | 278 | if ((unsigned)nibbles > UNITY_MAX_NIBBLES) 279 | { 280 | nibbles = UNITY_MAX_NIBBLES; 281 | } 282 | 283 | while (nibbles > 0) 284 | { 285 | nibbles--; 286 | nibble = (int)(number >> (nibbles * 4)) & 0x0F; 287 | if (nibble <= 9) 288 | { 289 | UNITY_OUTPUT_CHAR((char)('0' + nibble)); 290 | } 291 | else 292 | { 293 | UNITY_OUTPUT_CHAR((char)('A' - 10 + nibble)); 294 | } 295 | } 296 | } 297 | 298 | /*-----------------------------------------------*/ 299 | void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number) 300 | { 301 | UNITY_UINT current_bit = (UNITY_UINT)1 << (UNITY_INT_WIDTH - 1); 302 | UNITY_INT32 i; 303 | 304 | for (i = 0; i < UNITY_INT_WIDTH; i++) 305 | { 306 | if (current_bit & mask) 307 | { 308 | if (current_bit & number) 309 | { 310 | UNITY_OUTPUT_CHAR('1'); 311 | } 312 | else 313 | { 314 | UNITY_OUTPUT_CHAR('0'); 315 | } 316 | } 317 | else 318 | { 319 | UNITY_OUTPUT_CHAR('X'); 320 | } 321 | current_bit = current_bit >> 1; 322 | } 323 | } 324 | 325 | /*-----------------------------------------------*/ 326 | #ifndef UNITY_EXCLUDE_FLOAT_PRINT 327 | /* 328 | * This function prints a floating-point value in a format similar to 329 | * printf("%.7g") on a single-precision machine or printf("%.9g") on a 330 | * double-precision machine. The 7th digit won't always be totally correct 331 | * in single-precision operation (for that level of accuracy, a more 332 | * complicated algorithm would be needed). 333 | */ 334 | void UnityPrintFloat(const UNITY_DOUBLE input_number) 335 | { 336 | #ifdef UNITY_INCLUDE_DOUBLE 337 | static const int sig_digits = 9; 338 | static const UNITY_INT32 min_scaled = 100000000; 339 | static const UNITY_INT32 max_scaled = 1000000000; 340 | #else 341 | static const int sig_digits = 7; 342 | static const UNITY_INT32 min_scaled = 1000000; 343 | static const UNITY_INT32 max_scaled = 10000000; 344 | #endif 345 | 346 | UNITY_DOUBLE number = input_number; 347 | 348 | /* print minus sign (does not handle negative zero) */ 349 | if (number < 0.0f) 350 | { 351 | UNITY_OUTPUT_CHAR('-'); 352 | number = -number; 353 | } 354 | 355 | /* handle zero, NaN, and +/- infinity */ 356 | if (number == 0.0f) 357 | { 358 | UnityPrint("0"); 359 | } 360 | else if (UNITY_IS_NAN(number)) 361 | { 362 | UnityPrint("nan"); 363 | } 364 | else if (UNITY_IS_INF(number)) 365 | { 366 | UnityPrint("inf"); 367 | } 368 | else 369 | { 370 | UNITY_INT32 n_int = 0; 371 | UNITY_INT32 n; 372 | int exponent = 0; 373 | int decimals; 374 | int digits; 375 | char buf[16] = {0}; 376 | 377 | /* 378 | * Scale up or down by powers of 10. To minimize rounding error, 379 | * start with a factor/divisor of 10^10, which is the largest 380 | * power of 10 that can be represented exactly. Finally, compute 381 | * (exactly) the remaining power of 10 and perform one more 382 | * multiplication or division. 383 | */ 384 | if (number < 1.0f) 385 | { 386 | UNITY_DOUBLE factor = 1.0f; 387 | 388 | while (number < (UNITY_DOUBLE)max_scaled / 1e10f) { number *= 1e10f; exponent -= 10; } 389 | while (number * factor < (UNITY_DOUBLE)min_scaled) { factor *= 10.0f; exponent--; } 390 | 391 | number *= factor; 392 | } 393 | else if (number > (UNITY_DOUBLE)max_scaled) 394 | { 395 | UNITY_DOUBLE divisor = 1.0f; 396 | 397 | while (number > (UNITY_DOUBLE)min_scaled * 1e10f) { number /= 1e10f; exponent += 10; } 398 | while (number / divisor > (UNITY_DOUBLE)max_scaled) { divisor *= 10.0f; exponent++; } 399 | 400 | number /= divisor; 401 | } 402 | else 403 | { 404 | /* 405 | * In this range, we can split off the integer part before 406 | * doing any multiplications. This reduces rounding error by 407 | * freeing up significant bits in the fractional part. 408 | */ 409 | UNITY_DOUBLE factor = 1.0f; 410 | n_int = (UNITY_INT32)number; 411 | number -= (UNITY_DOUBLE)n_int; 412 | 413 | while (n_int < min_scaled) { n_int *= 10; factor *= 10.0f; exponent--; } 414 | 415 | number *= factor; 416 | } 417 | 418 | /* round to nearest integer */ 419 | n = ((UNITY_INT32)(number + number) + 1) / 2; 420 | 421 | #ifndef UNITY_ROUND_TIES_AWAY_FROM_ZERO 422 | /* round to even if exactly between two integers */ 423 | if ((n & 1) && (((UNITY_DOUBLE)n - number) == 0.5f)) 424 | n--; 425 | #endif 426 | 427 | n += n_int; 428 | 429 | if (n >= max_scaled) 430 | { 431 | n = min_scaled; 432 | exponent++; 433 | } 434 | 435 | /* determine where to place decimal point */ 436 | decimals = ((exponent <= 0) && (exponent >= -(sig_digits + 3))) ? (-exponent) : (sig_digits - 1); 437 | exponent += decimals; 438 | 439 | /* truncate trailing zeroes after decimal point */ 440 | while ((decimals > 0) && ((n % 10) == 0)) 441 | { 442 | n /= 10; 443 | decimals--; 444 | } 445 | 446 | /* build up buffer in reverse order */ 447 | digits = 0; 448 | while ((n != 0) || (digits <= decimals)) 449 | { 450 | buf[digits++] = (char)('0' + n % 10); 451 | n /= 10; 452 | } 453 | 454 | /* print out buffer (backwards) */ 455 | while (digits > 0) 456 | { 457 | if (digits == decimals) 458 | { 459 | UNITY_OUTPUT_CHAR('.'); 460 | } 461 | UNITY_OUTPUT_CHAR(buf[--digits]); 462 | } 463 | 464 | /* print exponent if needed */ 465 | if (exponent != 0) 466 | { 467 | UNITY_OUTPUT_CHAR('e'); 468 | 469 | if (exponent < 0) 470 | { 471 | UNITY_OUTPUT_CHAR('-'); 472 | exponent = -exponent; 473 | } 474 | else 475 | { 476 | UNITY_OUTPUT_CHAR('+'); 477 | } 478 | 479 | digits = 0; 480 | while ((exponent != 0) || (digits < 2)) 481 | { 482 | buf[digits++] = (char)('0' + exponent % 10); 483 | exponent /= 10; 484 | } 485 | while (digits > 0) 486 | { 487 | UNITY_OUTPUT_CHAR(buf[--digits]); 488 | } 489 | } 490 | } 491 | } 492 | #endif /* ! UNITY_EXCLUDE_FLOAT_PRINT */ 493 | 494 | /*-----------------------------------------------*/ 495 | static void UnityTestResultsBegin(const char* file, const UNITY_LINE_TYPE line) 496 | { 497 | #ifdef UNITY_OUTPUT_FOR_ECLIPSE 498 | UNITY_OUTPUT_CHAR('('); 499 | UnityPrint(file); 500 | UNITY_OUTPUT_CHAR(':'); 501 | UnityPrintNumber((UNITY_INT)line); 502 | UNITY_OUTPUT_CHAR(')'); 503 | UNITY_OUTPUT_CHAR(' '); 504 | UnityPrint(Unity.CurrentTestName); 505 | UNITY_OUTPUT_CHAR(':'); 506 | #else 507 | #ifdef UNITY_OUTPUT_FOR_IAR_WORKBENCH 508 | UnityPrint("'); 514 | UnityPrint(Unity.CurrentTestName); 515 | UnityPrint(" "); 516 | #else 517 | #ifdef UNITY_OUTPUT_FOR_QT_CREATOR 518 | UnityPrint("file://"); 519 | UnityPrint(file); 520 | UNITY_OUTPUT_CHAR(':'); 521 | UnityPrintNumber((UNITY_INT)line); 522 | UNITY_OUTPUT_CHAR(' '); 523 | UnityPrint(Unity.CurrentTestName); 524 | UNITY_OUTPUT_CHAR(':'); 525 | #else 526 | UnityPrint(file); 527 | UNITY_OUTPUT_CHAR(':'); 528 | UnityPrintNumber((UNITY_INT)line); 529 | UNITY_OUTPUT_CHAR(':'); 530 | UnityPrint(Unity.CurrentTestName); 531 | UNITY_OUTPUT_CHAR(':'); 532 | #endif 533 | #endif 534 | #endif 535 | } 536 | 537 | /*-----------------------------------------------*/ 538 | static void UnityTestResultsFailBegin(const UNITY_LINE_TYPE line) 539 | { 540 | UnityTestResultsBegin(Unity.TestFile, line); 541 | UnityPrint(UnityStrFail); 542 | UNITY_OUTPUT_CHAR(':'); 543 | } 544 | 545 | /*-----------------------------------------------*/ 546 | void UnityConcludeTest(void) 547 | { 548 | if (Unity.CurrentTestIgnored) 549 | { 550 | Unity.TestIgnores++; 551 | } 552 | else if (!Unity.CurrentTestFailed) 553 | { 554 | UnityTestResultsBegin(Unity.TestFile, Unity.CurrentTestLineNumber); 555 | UnityPrint(UnityStrPass); 556 | } 557 | else 558 | { 559 | Unity.TestFailures++; 560 | } 561 | 562 | Unity.CurrentTestFailed = 0; 563 | Unity.CurrentTestIgnored = 0; 564 | UNITY_PRINT_EXEC_TIME(); 565 | UNITY_PRINT_EOL(); 566 | UNITY_FLUSH_CALL(); 567 | } 568 | 569 | /*-----------------------------------------------*/ 570 | static void UnityAddMsgIfSpecified(const char* msg) 571 | { 572 | #ifdef UNITY_PRINT_TEST_CONTEXT 573 | UnityPrint(UnityStrSpacer); 574 | UNITY_PRINT_TEST_CONTEXT(); 575 | #endif 576 | #ifndef UNITY_EXCLUDE_DETAILS 577 | if (Unity.CurrentDetail1) 578 | { 579 | UnityPrint(UnityStrSpacer); 580 | UnityPrint(UnityStrDetail1Name); 581 | UnityPrint(Unity.CurrentDetail1); 582 | if (Unity.CurrentDetail2) 583 | { 584 | UnityPrint(UnityStrDetail2Name); 585 | UnityPrint(Unity.CurrentDetail2); 586 | } 587 | } 588 | #endif 589 | if (msg) 590 | { 591 | UnityPrint(UnityStrSpacer); 592 | UnityPrint(msg); 593 | } 594 | } 595 | 596 | /*-----------------------------------------------*/ 597 | static void UnityPrintExpectedAndActualStrings(const char* expected, const char* actual) 598 | { 599 | UnityPrint(UnityStrExpected); 600 | if (expected != NULL) 601 | { 602 | UNITY_OUTPUT_CHAR('\''); 603 | UnityPrint(expected); 604 | UNITY_OUTPUT_CHAR('\''); 605 | } 606 | else 607 | { 608 | UnityPrint(UnityStrNull); 609 | } 610 | UnityPrint(UnityStrWas); 611 | if (actual != NULL) 612 | { 613 | UNITY_OUTPUT_CHAR('\''); 614 | UnityPrint(actual); 615 | UNITY_OUTPUT_CHAR('\''); 616 | } 617 | else 618 | { 619 | UnityPrint(UnityStrNull); 620 | } 621 | } 622 | 623 | /*-----------------------------------------------*/ 624 | static void UnityPrintExpectedAndActualStringsLen(const char* expected, 625 | const char* actual, 626 | const UNITY_UINT32 length) 627 | { 628 | UnityPrint(UnityStrExpected); 629 | if (expected != NULL) 630 | { 631 | UNITY_OUTPUT_CHAR('\''); 632 | UnityPrintLen(expected, length); 633 | UNITY_OUTPUT_CHAR('\''); 634 | } 635 | else 636 | { 637 | UnityPrint(UnityStrNull); 638 | } 639 | UnityPrint(UnityStrWas); 640 | if (actual != NULL) 641 | { 642 | UNITY_OUTPUT_CHAR('\''); 643 | UnityPrintLen(actual, length); 644 | UNITY_OUTPUT_CHAR('\''); 645 | } 646 | else 647 | { 648 | UnityPrint(UnityStrNull); 649 | } 650 | } 651 | 652 | /*----------------------------------------------- 653 | * Assertion & Control Helpers 654 | *-----------------------------------------------*/ 655 | 656 | /*-----------------------------------------------*/ 657 | static int UnityIsOneArrayNull(UNITY_INTERNAL_PTR expected, 658 | UNITY_INTERNAL_PTR actual, 659 | const UNITY_LINE_TYPE lineNumber, 660 | const char* msg) 661 | { 662 | /* Both are NULL or same pointer */ 663 | if (expected == actual) { return 0; } 664 | 665 | /* print and return true if just expected is NULL */ 666 | if (expected == NULL) 667 | { 668 | UnityTestResultsFailBegin(lineNumber); 669 | UnityPrint(UnityStrNullPointerForExpected); 670 | UnityAddMsgIfSpecified(msg); 671 | return 1; 672 | } 673 | 674 | /* print and return true if just actual is NULL */ 675 | if (actual == NULL) 676 | { 677 | UnityTestResultsFailBegin(lineNumber); 678 | UnityPrint(UnityStrNullPointerForActual); 679 | UnityAddMsgIfSpecified(msg); 680 | return 1; 681 | } 682 | 683 | return 0; /* return false if neither is NULL */ 684 | } 685 | 686 | /*----------------------------------------------- 687 | * Assertion Functions 688 | *-----------------------------------------------*/ 689 | 690 | /*-----------------------------------------------*/ 691 | void UnityAssertBits(const UNITY_INT mask, 692 | const UNITY_INT expected, 693 | const UNITY_INT actual, 694 | const char* msg, 695 | const UNITY_LINE_TYPE lineNumber) 696 | { 697 | RETURN_IF_FAIL_OR_IGNORE; 698 | 699 | if ((mask & expected) != (mask & actual)) 700 | { 701 | UnityTestResultsFailBegin(lineNumber); 702 | UnityPrint(UnityStrExpected); 703 | UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)expected); 704 | UnityPrint(UnityStrWas); 705 | UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)actual); 706 | UnityAddMsgIfSpecified(msg); 707 | UNITY_FAIL_AND_BAIL; 708 | } 709 | } 710 | 711 | /*-----------------------------------------------*/ 712 | void UnityAssertEqualNumber(const UNITY_INT expected, 713 | const UNITY_INT actual, 714 | const char* msg, 715 | const UNITY_LINE_TYPE lineNumber, 716 | const UNITY_DISPLAY_STYLE_T style) 717 | { 718 | RETURN_IF_FAIL_OR_IGNORE; 719 | 720 | if (expected != actual) 721 | { 722 | UnityTestResultsFailBegin(lineNumber); 723 | UnityPrint(UnityStrExpected); 724 | UnityPrintNumberByStyle(expected, style); 725 | UnityPrint(UnityStrWas); 726 | UnityPrintNumberByStyle(actual, style); 727 | UnityAddMsgIfSpecified(msg); 728 | UNITY_FAIL_AND_BAIL; 729 | } 730 | } 731 | 732 | /*-----------------------------------------------*/ 733 | void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, 734 | const UNITY_INT actual, 735 | const UNITY_COMPARISON_T compare, 736 | const char *msg, 737 | const UNITY_LINE_TYPE lineNumber, 738 | const UNITY_DISPLAY_STYLE_T style) 739 | { 740 | int failed = 0; 741 | RETURN_IF_FAIL_OR_IGNORE; 742 | 743 | if ((threshold == actual) && (compare & UNITY_EQUAL_TO)) { return; } 744 | if ((threshold == actual)) { failed = 1; } 745 | 746 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 747 | { 748 | if ((actual > threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } 749 | if ((actual < threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } 750 | } 751 | else /* UINT or HEX */ 752 | { 753 | if (((UNITY_UINT)actual > (UNITY_UINT)threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } 754 | if (((UNITY_UINT)actual < (UNITY_UINT)threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } 755 | } 756 | 757 | if (failed) 758 | { 759 | UnityTestResultsFailBegin(lineNumber); 760 | UnityPrint(UnityStrExpected); 761 | UnityPrintNumberByStyle(actual, style); 762 | if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } 763 | if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } 764 | if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } 765 | if (compare == UNITY_NOT_EQUAL) { UnityPrint(UnityStrNotEqual); } 766 | UnityPrintNumberByStyle(threshold, style); 767 | UnityAddMsgIfSpecified(msg); 768 | UNITY_FAIL_AND_BAIL; 769 | } 770 | } 771 | 772 | #define UnityPrintPointlessAndBail() \ 773 | do { \ 774 | UnityTestResultsFailBegin(lineNumber); \ 775 | UnityPrint(UnityStrPointless); \ 776 | UnityAddMsgIfSpecified(msg); \ 777 | UNITY_FAIL_AND_BAIL; \ 778 | } while (0) 779 | 780 | /*-----------------------------------------------*/ 781 | void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, 782 | UNITY_INTERNAL_PTR actual, 783 | const UNITY_UINT32 num_elements, 784 | const char* msg, 785 | const UNITY_LINE_TYPE lineNumber, 786 | const UNITY_DISPLAY_STYLE_T style, 787 | const UNITY_FLAGS_T flags) 788 | { 789 | UNITY_UINT32 elements = num_elements; 790 | unsigned int length = style & 0xF; 791 | unsigned int increment = 0; 792 | 793 | RETURN_IF_FAIL_OR_IGNORE; 794 | 795 | if (num_elements == 0) 796 | { 797 | #ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY 798 | UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); 799 | #else 800 | UnityPrintPointlessAndBail(); 801 | #endif 802 | } 803 | 804 | if (expected == actual) 805 | { 806 | return; /* Both are NULL or same pointer */ 807 | } 808 | 809 | if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) 810 | { 811 | UNITY_FAIL_AND_BAIL; 812 | } 813 | 814 | while ((elements > 0) && (elements--)) 815 | { 816 | UNITY_INT expect_val; 817 | UNITY_INT actual_val; 818 | 819 | switch (length) 820 | { 821 | case 1: 822 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; 823 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; 824 | if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) 825 | { 826 | expect_val &= 0x000000FF; 827 | actual_val &= 0x000000FF; 828 | } 829 | increment = sizeof(UNITY_INT8); 830 | break; 831 | 832 | case 2: 833 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; 834 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; 835 | if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) 836 | { 837 | expect_val &= 0x0000FFFF; 838 | actual_val &= 0x0000FFFF; 839 | } 840 | increment = sizeof(UNITY_INT16); 841 | break; 842 | 843 | #ifdef UNITY_SUPPORT_64 844 | case 8: 845 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; 846 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; 847 | increment = sizeof(UNITY_INT64); 848 | break; 849 | #endif 850 | 851 | default: /* default is length 4 bytes */ 852 | case 4: 853 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; 854 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; 855 | #ifdef UNITY_SUPPORT_64 856 | if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) 857 | { 858 | expect_val &= 0x00000000FFFFFFFF; 859 | actual_val &= 0x00000000FFFFFFFF; 860 | } 861 | #endif 862 | increment = sizeof(UNITY_INT32); 863 | length = 4; 864 | break; 865 | } 866 | 867 | if (expect_val != actual_val) 868 | { 869 | if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) 870 | { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ 871 | UNITY_INT mask = 1; 872 | mask = (mask << 8 * length) - 1; 873 | expect_val &= mask; 874 | actual_val &= mask; 875 | } 876 | UnityTestResultsFailBegin(lineNumber); 877 | UnityPrint(UnityStrElement); 878 | UnityPrintNumberUnsigned(num_elements - elements - 1); 879 | UnityPrint(UnityStrExpected); 880 | UnityPrintNumberByStyle(expect_val, style); 881 | UnityPrint(UnityStrWas); 882 | UnityPrintNumberByStyle(actual_val, style); 883 | UnityAddMsgIfSpecified(msg); 884 | UNITY_FAIL_AND_BAIL; 885 | } 886 | /* Walk through array by incrementing the pointers */ 887 | if (flags == UNITY_ARRAY_TO_ARRAY) 888 | { 889 | expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); 890 | } 891 | actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); 892 | } 893 | } 894 | 895 | /*-----------------------------------------------*/ 896 | #ifndef UNITY_EXCLUDE_FLOAT 897 | /* Wrap this define in a function with variable types as float or double */ 898 | #define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \ 899 | if (UNITY_IS_INF(expected) && UNITY_IS_INF(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ 900 | if (UNITY_NAN_CHECK) return 1; \ 901 | (diff) = (actual) - (expected); \ 902 | if ((diff) < 0) (diff) = -(diff); \ 903 | if ((delta) < 0) (delta) = -(delta); \ 904 | return !(UNITY_IS_NAN(diff) || UNITY_IS_INF(diff) || ((diff) > (delta))) 905 | /* This first part of this condition will catch any NaN or Infinite values */ 906 | #ifndef UNITY_NAN_NOT_EQUAL_NAN 907 | #define UNITY_NAN_CHECK UNITY_IS_NAN(expected) && UNITY_IS_NAN(actual) 908 | #else 909 | #define UNITY_NAN_CHECK 0 910 | #endif 911 | 912 | #ifndef UNITY_EXCLUDE_FLOAT_PRINT 913 | #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ 914 | do { \ 915 | UnityPrint(UnityStrExpected); \ 916 | UnityPrintFloat(expected); \ 917 | UnityPrint(UnityStrWas); \ 918 | UnityPrintFloat(actual); \ 919 | } while (0) 920 | #else 921 | #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ 922 | UnityPrint(UnityStrDelta) 923 | #endif /* UNITY_EXCLUDE_FLOAT_PRINT */ 924 | 925 | /*-----------------------------------------------*/ 926 | static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOAT actual) 927 | { 928 | UNITY_FLOAT diff; 929 | UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); 930 | } 931 | 932 | /*-----------------------------------------------*/ 933 | void UnityAssertWithinFloatArray(const UNITY_FLOAT delta, 934 | UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, 935 | UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, 936 | const UNITY_UINT32 num_elements, 937 | const char* msg, 938 | const UNITY_LINE_TYPE lineNumber, 939 | const UNITY_FLAGS_T flags) 940 | { 941 | UNITY_UINT32 elements = num_elements; 942 | UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected; 943 | UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual; 944 | UNITY_FLOAT in_delta = delta; 945 | UNITY_FLOAT current_element_delta = delta; 946 | 947 | RETURN_IF_FAIL_OR_IGNORE; 948 | 949 | if (elements == 0) 950 | { 951 | #ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY 952 | UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); 953 | #else 954 | UnityPrintPointlessAndBail(); 955 | #endif 956 | } 957 | 958 | if (UNITY_IS_INF(in_delta)) 959 | { 960 | return; /* Arrays will be force equal with infinite delta */ 961 | } 962 | 963 | if (UNITY_IS_NAN(in_delta)) 964 | { 965 | /* Delta must be correct number */ 966 | UnityPrintPointlessAndBail(); 967 | } 968 | 969 | if (expected == actual) 970 | { 971 | return; /* Both are NULL or same pointer */ 972 | } 973 | 974 | if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) 975 | { 976 | UNITY_FAIL_AND_BAIL; 977 | } 978 | 979 | /* fix delta sign if need */ 980 | if (in_delta < 0) 981 | { 982 | in_delta = -in_delta; 983 | } 984 | 985 | while (elements--) 986 | { 987 | current_element_delta = *ptr_expected * UNITY_FLOAT_PRECISION; 988 | 989 | if (current_element_delta < 0) 990 | { 991 | /* fix delta sign for correct calculations */ 992 | current_element_delta = -current_element_delta; 993 | } 994 | 995 | if (!UnityFloatsWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual)) 996 | { 997 | UnityTestResultsFailBegin(lineNumber); 998 | UnityPrint(UnityStrElement); 999 | UnityPrintNumberUnsigned(num_elements - elements - 1); 1000 | UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual); 1001 | UnityAddMsgIfSpecified(msg); 1002 | UNITY_FAIL_AND_BAIL; 1003 | } 1004 | if (flags == UNITY_ARRAY_TO_ARRAY) 1005 | { 1006 | ptr_expected++; 1007 | } 1008 | ptr_actual++; 1009 | } 1010 | } 1011 | 1012 | /*-----------------------------------------------*/ 1013 | void UnityAssertFloatsWithin(const UNITY_FLOAT delta, 1014 | const UNITY_FLOAT expected, 1015 | const UNITY_FLOAT actual, 1016 | const char* msg, 1017 | const UNITY_LINE_TYPE lineNumber) 1018 | { 1019 | RETURN_IF_FAIL_OR_IGNORE; 1020 | 1021 | 1022 | if (!UnityFloatsWithin(delta, expected, actual)) 1023 | { 1024 | UnityTestResultsFailBegin(lineNumber); 1025 | UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual); 1026 | UnityAddMsgIfSpecified(msg); 1027 | UNITY_FAIL_AND_BAIL; 1028 | } 1029 | } 1030 | 1031 | /*-----------------------------------------------*/ 1032 | void UnityAssertFloatsNotWithin(const UNITY_FLOAT delta, 1033 | const UNITY_FLOAT expected, 1034 | const UNITY_FLOAT actual, 1035 | const char* msg, 1036 | const UNITY_LINE_TYPE lineNumber) 1037 | { 1038 | RETURN_IF_FAIL_OR_IGNORE; 1039 | 1040 | if (UnityFloatsWithin(delta, expected, actual)) 1041 | { 1042 | UnityTestResultsFailBegin(lineNumber); 1043 | UnityPrint(UnityStrExpected); 1044 | UnityPrintFloat((UNITY_DOUBLE)expected); 1045 | UnityPrint(UnityStrNotEqual); 1046 | UnityPrintFloat((UNITY_DOUBLE)actual); 1047 | UnityAddMsgIfSpecified(msg); 1048 | UNITY_FAIL_AND_BAIL; 1049 | } 1050 | } 1051 | 1052 | /*-----------------------------------------------*/ 1053 | void UnityAssertGreaterOrLessFloat(const UNITY_FLOAT threshold, 1054 | const UNITY_FLOAT actual, 1055 | const UNITY_COMPARISON_T compare, 1056 | const char* msg, 1057 | const UNITY_LINE_TYPE lineNumber) 1058 | { 1059 | int failed; 1060 | 1061 | RETURN_IF_FAIL_OR_IGNORE; 1062 | 1063 | failed = 0; 1064 | 1065 | /* Checking for "not success" rather than failure to get the right result for NaN */ 1066 | if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } 1067 | if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } 1068 | 1069 | if ((compare & UNITY_EQUAL_TO) && UnityFloatsWithin(threshold * UNITY_FLOAT_PRECISION, threshold, actual)) { failed = 0; } 1070 | 1071 | if (failed) 1072 | { 1073 | UnityTestResultsFailBegin(lineNumber); 1074 | UnityPrint(UnityStrExpected); 1075 | UnityPrintFloat(actual); 1076 | if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } 1077 | if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } 1078 | if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } 1079 | UnityPrintFloat(threshold); 1080 | UnityAddMsgIfSpecified(msg); 1081 | UNITY_FAIL_AND_BAIL; 1082 | } 1083 | } 1084 | 1085 | /*-----------------------------------------------*/ 1086 | void UnityAssertFloatSpecial(const UNITY_FLOAT actual, 1087 | const char* msg, 1088 | const UNITY_LINE_TYPE lineNumber, 1089 | const UNITY_FLOAT_TRAIT_T style) 1090 | { 1091 | const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; 1092 | UNITY_INT should_be_trait = ((UNITY_INT)style & 1); 1093 | UNITY_INT is_trait = !should_be_trait; 1094 | UNITY_INT trait_index = (UNITY_INT)(style >> 1); 1095 | 1096 | RETURN_IF_FAIL_OR_IGNORE; 1097 | 1098 | switch (style) 1099 | { 1100 | case UNITY_FLOAT_IS_INF: 1101 | case UNITY_FLOAT_IS_NOT_INF: 1102 | is_trait = UNITY_IS_INF(actual) && (actual > 0); 1103 | break; 1104 | case UNITY_FLOAT_IS_NEG_INF: 1105 | case UNITY_FLOAT_IS_NOT_NEG_INF: 1106 | is_trait = UNITY_IS_INF(actual) && (actual < 0); 1107 | break; 1108 | 1109 | case UNITY_FLOAT_IS_NAN: 1110 | case UNITY_FLOAT_IS_NOT_NAN: 1111 | is_trait = UNITY_IS_NAN(actual) ? 1 : 0; 1112 | break; 1113 | 1114 | case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ 1115 | case UNITY_FLOAT_IS_NOT_DET: 1116 | is_trait = !UNITY_IS_INF(actual) && !UNITY_IS_NAN(actual); 1117 | break; 1118 | 1119 | case UNITY_FLOAT_INVALID_TRAIT: /* Supress warning */ 1120 | default: /* including UNITY_FLOAT_INVALID_TRAIT */ 1121 | trait_index = 0; 1122 | trait_names[0] = UnityStrInvalidFloatTrait; 1123 | break; 1124 | } 1125 | 1126 | if (is_trait != should_be_trait) 1127 | { 1128 | UnityTestResultsFailBegin(lineNumber); 1129 | UnityPrint(UnityStrExpected); 1130 | if (!should_be_trait) 1131 | { 1132 | UnityPrint(UnityStrNot); 1133 | } 1134 | UnityPrint(trait_names[trait_index]); 1135 | UnityPrint(UnityStrWas); 1136 | #ifndef UNITY_EXCLUDE_FLOAT_PRINT 1137 | UnityPrintFloat((UNITY_DOUBLE)actual); 1138 | #else 1139 | if (should_be_trait) 1140 | { 1141 | UnityPrint(UnityStrNot); 1142 | } 1143 | UnityPrint(trait_names[trait_index]); 1144 | #endif 1145 | UnityAddMsgIfSpecified(msg); 1146 | UNITY_FAIL_AND_BAIL; 1147 | } 1148 | } 1149 | 1150 | #endif /* not UNITY_EXCLUDE_FLOAT */ 1151 | 1152 | /*-----------------------------------------------*/ 1153 | #ifndef UNITY_EXCLUDE_DOUBLE 1154 | static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_DOUBLE actual) 1155 | { 1156 | UNITY_DOUBLE diff; 1157 | UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); 1158 | } 1159 | 1160 | /*-----------------------------------------------*/ 1161 | void UnityAssertWithinDoubleArray(const UNITY_DOUBLE delta, 1162 | UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, 1163 | UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, 1164 | const UNITY_UINT32 num_elements, 1165 | const char* msg, 1166 | const UNITY_LINE_TYPE lineNumber, 1167 | const UNITY_FLAGS_T flags) 1168 | { 1169 | UNITY_UINT32 elements = num_elements; 1170 | UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected; 1171 | UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual; 1172 | UNITY_DOUBLE in_delta = delta; 1173 | UNITY_DOUBLE current_element_delta = delta; 1174 | 1175 | RETURN_IF_FAIL_OR_IGNORE; 1176 | 1177 | if (elements == 0) 1178 | { 1179 | #ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY 1180 | UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); 1181 | #else 1182 | UnityPrintPointlessAndBail(); 1183 | #endif 1184 | } 1185 | 1186 | if (UNITY_IS_INF(in_delta)) 1187 | { 1188 | return; /* Arrays will be force equal with infinite delta */ 1189 | } 1190 | 1191 | if (UNITY_IS_NAN(in_delta)) 1192 | { 1193 | /* Delta must be correct number */ 1194 | UnityPrintPointlessAndBail(); 1195 | } 1196 | 1197 | if (expected == actual) 1198 | { 1199 | return; /* Both are NULL or same pointer */ 1200 | } 1201 | 1202 | if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) 1203 | { 1204 | UNITY_FAIL_AND_BAIL; 1205 | } 1206 | 1207 | /* fix delta sign if need */ 1208 | if (in_delta < 0) 1209 | { 1210 | in_delta = -in_delta; 1211 | } 1212 | 1213 | while (elements--) 1214 | { 1215 | current_element_delta = *ptr_expected * UNITY_DOUBLE_PRECISION; 1216 | 1217 | if (current_element_delta < 0) 1218 | { 1219 | /* fix delta sign for correct calculations */ 1220 | current_element_delta = -current_element_delta; 1221 | } 1222 | 1223 | if (!UnityDoublesWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual)) 1224 | { 1225 | UnityTestResultsFailBegin(lineNumber); 1226 | UnityPrint(UnityStrElement); 1227 | UnityPrintNumberUnsigned(num_elements - elements - 1); 1228 | UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual); 1229 | UnityAddMsgIfSpecified(msg); 1230 | UNITY_FAIL_AND_BAIL; 1231 | } 1232 | if (flags == UNITY_ARRAY_TO_ARRAY) 1233 | { 1234 | ptr_expected++; 1235 | } 1236 | ptr_actual++; 1237 | } 1238 | } 1239 | 1240 | /*-----------------------------------------------*/ 1241 | void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, 1242 | const UNITY_DOUBLE expected, 1243 | const UNITY_DOUBLE actual, 1244 | const char* msg, 1245 | const UNITY_LINE_TYPE lineNumber) 1246 | { 1247 | RETURN_IF_FAIL_OR_IGNORE; 1248 | 1249 | if (!UnityDoublesWithin(delta, expected, actual)) 1250 | { 1251 | UnityTestResultsFailBegin(lineNumber); 1252 | UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual); 1253 | UnityAddMsgIfSpecified(msg); 1254 | UNITY_FAIL_AND_BAIL; 1255 | } 1256 | } 1257 | 1258 | /*-----------------------------------------------*/ 1259 | void UnityAssertDoublesNotWithin(const UNITY_DOUBLE delta, 1260 | const UNITY_DOUBLE expected, 1261 | const UNITY_DOUBLE actual, 1262 | const char* msg, 1263 | const UNITY_LINE_TYPE lineNumber) 1264 | { 1265 | RETURN_IF_FAIL_OR_IGNORE; 1266 | 1267 | if (UnityDoublesWithin(delta, expected, actual)) 1268 | { 1269 | UnityTestResultsFailBegin(lineNumber); 1270 | UnityPrint(UnityStrExpected); 1271 | UnityPrintFloat((UNITY_DOUBLE)expected); 1272 | UnityPrint(UnityStrNotEqual); 1273 | UnityPrintFloat((UNITY_DOUBLE)actual); 1274 | UnityAddMsgIfSpecified(msg); 1275 | UNITY_FAIL_AND_BAIL; 1276 | } 1277 | } 1278 | 1279 | /*-----------------------------------------------*/ 1280 | void UnityAssertGreaterOrLessDouble(const UNITY_DOUBLE threshold, 1281 | const UNITY_DOUBLE actual, 1282 | const UNITY_COMPARISON_T compare, 1283 | const char* msg, 1284 | const UNITY_LINE_TYPE lineNumber) 1285 | { 1286 | int failed; 1287 | 1288 | RETURN_IF_FAIL_OR_IGNORE; 1289 | 1290 | failed = 0; 1291 | 1292 | /* Checking for "not success" rather than failure to get the right result for NaN */ 1293 | if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } 1294 | if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } 1295 | 1296 | if ((compare & UNITY_EQUAL_TO) && UnityDoublesWithin(threshold * UNITY_DOUBLE_PRECISION, threshold, actual)) { failed = 0; } 1297 | 1298 | if (failed) 1299 | { 1300 | UnityTestResultsFailBegin(lineNumber); 1301 | UnityPrint(UnityStrExpected); 1302 | UnityPrintFloat(actual); 1303 | if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } 1304 | if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } 1305 | if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } 1306 | UnityPrintFloat(threshold); 1307 | UnityAddMsgIfSpecified(msg); 1308 | UNITY_FAIL_AND_BAIL; 1309 | } 1310 | } 1311 | 1312 | /*-----------------------------------------------*/ 1313 | void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, 1314 | const char* msg, 1315 | const UNITY_LINE_TYPE lineNumber, 1316 | const UNITY_FLOAT_TRAIT_T style) 1317 | { 1318 | const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; 1319 | UNITY_INT should_be_trait = ((UNITY_INT)style & 1); 1320 | UNITY_INT is_trait = !should_be_trait; 1321 | UNITY_INT trait_index = (UNITY_INT)(style >> 1); 1322 | 1323 | RETURN_IF_FAIL_OR_IGNORE; 1324 | 1325 | switch (style) 1326 | { 1327 | case UNITY_FLOAT_IS_INF: 1328 | case UNITY_FLOAT_IS_NOT_INF: 1329 | is_trait = UNITY_IS_INF(actual) && (actual > 0); 1330 | break; 1331 | case UNITY_FLOAT_IS_NEG_INF: 1332 | case UNITY_FLOAT_IS_NOT_NEG_INF: 1333 | is_trait = UNITY_IS_INF(actual) && (actual < 0); 1334 | break; 1335 | 1336 | case UNITY_FLOAT_IS_NAN: 1337 | case UNITY_FLOAT_IS_NOT_NAN: 1338 | is_trait = UNITY_IS_NAN(actual) ? 1 : 0; 1339 | break; 1340 | 1341 | case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ 1342 | case UNITY_FLOAT_IS_NOT_DET: 1343 | is_trait = !UNITY_IS_INF(actual) && !UNITY_IS_NAN(actual); 1344 | break; 1345 | 1346 | case UNITY_FLOAT_INVALID_TRAIT: /* Supress warning */ 1347 | default: /* including UNITY_FLOAT_INVALID_TRAIT */ 1348 | trait_index = 0; 1349 | trait_names[0] = UnityStrInvalidFloatTrait; 1350 | break; 1351 | } 1352 | 1353 | if (is_trait != should_be_trait) 1354 | { 1355 | UnityTestResultsFailBegin(lineNumber); 1356 | UnityPrint(UnityStrExpected); 1357 | if (!should_be_trait) 1358 | { 1359 | UnityPrint(UnityStrNot); 1360 | } 1361 | UnityPrint(trait_names[trait_index]); 1362 | UnityPrint(UnityStrWas); 1363 | #ifndef UNITY_EXCLUDE_FLOAT_PRINT 1364 | UnityPrintFloat(actual); 1365 | #else 1366 | if (should_be_trait) 1367 | { 1368 | UnityPrint(UnityStrNot); 1369 | } 1370 | UnityPrint(trait_names[trait_index]); 1371 | #endif 1372 | UnityAddMsgIfSpecified(msg); 1373 | UNITY_FAIL_AND_BAIL; 1374 | } 1375 | } 1376 | 1377 | #endif /* not UNITY_EXCLUDE_DOUBLE */ 1378 | 1379 | /*-----------------------------------------------*/ 1380 | void UnityAssertNumbersWithin(const UNITY_UINT delta, 1381 | const UNITY_INT expected, 1382 | const UNITY_INT actual, 1383 | const char* msg, 1384 | const UNITY_LINE_TYPE lineNumber, 1385 | const UNITY_DISPLAY_STYLE_T style) 1386 | { 1387 | RETURN_IF_FAIL_OR_IGNORE; 1388 | 1389 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 1390 | { 1391 | if (actual > expected) 1392 | { 1393 | Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); 1394 | } 1395 | else 1396 | { 1397 | Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); 1398 | } 1399 | } 1400 | else 1401 | { 1402 | if ((UNITY_UINT)actual > (UNITY_UINT)expected) 1403 | { 1404 | Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); 1405 | } 1406 | else 1407 | { 1408 | Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); 1409 | } 1410 | } 1411 | 1412 | if (Unity.CurrentTestFailed) 1413 | { 1414 | UnityTestResultsFailBegin(lineNumber); 1415 | UnityPrint(UnityStrDelta); 1416 | UnityPrintNumberByStyle((UNITY_INT)delta, style); 1417 | UnityPrint(UnityStrExpected); 1418 | UnityPrintNumberByStyle(expected, style); 1419 | UnityPrint(UnityStrWas); 1420 | UnityPrintNumberByStyle(actual, style); 1421 | UnityAddMsgIfSpecified(msg); 1422 | UNITY_FAIL_AND_BAIL; 1423 | } 1424 | } 1425 | 1426 | /*-----------------------------------------------*/ 1427 | void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, 1428 | UNITY_INTERNAL_PTR expected, 1429 | UNITY_INTERNAL_PTR actual, 1430 | const UNITY_UINT32 num_elements, 1431 | const char* msg, 1432 | const UNITY_LINE_TYPE lineNumber, 1433 | const UNITY_DISPLAY_STYLE_T style, 1434 | const UNITY_FLAGS_T flags) 1435 | { 1436 | UNITY_UINT32 elements = num_elements; 1437 | unsigned int length = style & 0xF; 1438 | unsigned int increment = 0; 1439 | 1440 | RETURN_IF_FAIL_OR_IGNORE; 1441 | 1442 | if (num_elements == 0) 1443 | { 1444 | #ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY 1445 | UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); 1446 | #else 1447 | UnityPrintPointlessAndBail(); 1448 | #endif 1449 | } 1450 | 1451 | if (expected == actual) 1452 | { 1453 | return; /* Both are NULL or same pointer */ 1454 | } 1455 | 1456 | if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) 1457 | { 1458 | UNITY_FAIL_AND_BAIL; 1459 | } 1460 | 1461 | while ((elements > 0) && (elements--)) 1462 | { 1463 | UNITY_INT expect_val; 1464 | UNITY_INT actual_val; 1465 | 1466 | switch (length) 1467 | { 1468 | case 1: 1469 | /* fixing problems with signed overflow on unsigned numbers */ 1470 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 1471 | { 1472 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; 1473 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; 1474 | increment = sizeof(UNITY_INT8); 1475 | } 1476 | else 1477 | { 1478 | expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)expected; 1479 | actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)actual; 1480 | increment = sizeof(UNITY_UINT8); 1481 | } 1482 | break; 1483 | 1484 | case 2: 1485 | /* fixing problems with signed overflow on unsigned numbers */ 1486 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 1487 | { 1488 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; 1489 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; 1490 | increment = sizeof(UNITY_INT16); 1491 | } 1492 | else 1493 | { 1494 | expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)expected; 1495 | actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)actual; 1496 | increment = sizeof(UNITY_UINT16); 1497 | } 1498 | break; 1499 | 1500 | #ifdef UNITY_SUPPORT_64 1501 | case 8: 1502 | /* fixing problems with signed overflow on unsigned numbers */ 1503 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 1504 | { 1505 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; 1506 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; 1507 | increment = sizeof(UNITY_INT64); 1508 | } 1509 | else 1510 | { 1511 | expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)expected; 1512 | actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)actual; 1513 | increment = sizeof(UNITY_UINT64); 1514 | } 1515 | break; 1516 | #endif 1517 | 1518 | default: /* default is length 4 bytes */ 1519 | case 4: 1520 | /* fixing problems with signed overflow on unsigned numbers */ 1521 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 1522 | { 1523 | expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; 1524 | actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; 1525 | increment = sizeof(UNITY_INT32); 1526 | } 1527 | else 1528 | { 1529 | expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)expected; 1530 | actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)actual; 1531 | increment = sizeof(UNITY_UINT32); 1532 | } 1533 | length = 4; 1534 | break; 1535 | } 1536 | 1537 | if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) 1538 | { 1539 | if (actual_val > expect_val) 1540 | { 1541 | Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); 1542 | } 1543 | else 1544 | { 1545 | Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); 1546 | } 1547 | } 1548 | else 1549 | { 1550 | if ((UNITY_UINT)actual_val > (UNITY_UINT)expect_val) 1551 | { 1552 | Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); 1553 | } 1554 | else 1555 | { 1556 | Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); 1557 | } 1558 | } 1559 | 1560 | if (Unity.CurrentTestFailed) 1561 | { 1562 | if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) 1563 | { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ 1564 | UNITY_INT mask = 1; 1565 | mask = (mask << 8 * length) - 1; 1566 | expect_val &= mask; 1567 | actual_val &= mask; 1568 | } 1569 | UnityTestResultsFailBegin(lineNumber); 1570 | UnityPrint(UnityStrDelta); 1571 | UnityPrintNumberByStyle((UNITY_INT)delta, style); 1572 | UnityPrint(UnityStrElement); 1573 | UnityPrintNumberUnsigned(num_elements - elements - 1); 1574 | UnityPrint(UnityStrExpected); 1575 | UnityPrintNumberByStyle(expect_val, style); 1576 | UnityPrint(UnityStrWas); 1577 | UnityPrintNumberByStyle(actual_val, style); 1578 | UnityAddMsgIfSpecified(msg); 1579 | UNITY_FAIL_AND_BAIL; 1580 | } 1581 | /* Walk through array by incrementing the pointers */ 1582 | if (flags == UNITY_ARRAY_TO_ARRAY) 1583 | { 1584 | expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); 1585 | } 1586 | actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); 1587 | } 1588 | } 1589 | 1590 | /*-----------------------------------------------*/ 1591 | void UnityAssertEqualString(const char* expected, 1592 | const char* actual, 1593 | const char* msg, 1594 | const UNITY_LINE_TYPE lineNumber) 1595 | { 1596 | UNITY_UINT32 i; 1597 | 1598 | RETURN_IF_FAIL_OR_IGNORE; 1599 | 1600 | /* if both pointers not null compare the strings */ 1601 | if (expected && actual) 1602 | { 1603 | for (i = 0; expected[i] || actual[i]; i++) 1604 | { 1605 | if (expected[i] != actual[i]) 1606 | { 1607 | Unity.CurrentTestFailed = 1; 1608 | break; 1609 | } 1610 | } 1611 | } 1612 | else 1613 | { /* fail if either null but not if both */ 1614 | if (expected || actual) 1615 | { 1616 | Unity.CurrentTestFailed = 1; 1617 | } 1618 | } 1619 | 1620 | if (Unity.CurrentTestFailed) 1621 | { 1622 | UnityTestResultsFailBegin(lineNumber); 1623 | UnityPrintExpectedAndActualStrings(expected, actual); 1624 | UnityAddMsgIfSpecified(msg); 1625 | UNITY_FAIL_AND_BAIL; 1626 | } 1627 | } 1628 | 1629 | /*-----------------------------------------------*/ 1630 | void UnityAssertEqualStringLen(const char* expected, 1631 | const char* actual, 1632 | const UNITY_UINT32 length, 1633 | const char* msg, 1634 | const UNITY_LINE_TYPE lineNumber) 1635 | { 1636 | UNITY_UINT32 i; 1637 | 1638 | RETURN_IF_FAIL_OR_IGNORE; 1639 | 1640 | /* if both pointers not null compare the strings */ 1641 | if (expected && actual) 1642 | { 1643 | for (i = 0; (i < length) && (expected[i] || actual[i]); i++) 1644 | { 1645 | if (expected[i] != actual[i]) 1646 | { 1647 | Unity.CurrentTestFailed = 1; 1648 | break; 1649 | } 1650 | } 1651 | } 1652 | else 1653 | { /* fail if either null but not if both */ 1654 | if (expected || actual) 1655 | { 1656 | Unity.CurrentTestFailed = 1; 1657 | } 1658 | } 1659 | 1660 | if (Unity.CurrentTestFailed) 1661 | { 1662 | UnityTestResultsFailBegin(lineNumber); 1663 | UnityPrintExpectedAndActualStringsLen(expected, actual, length); 1664 | UnityAddMsgIfSpecified(msg); 1665 | UNITY_FAIL_AND_BAIL; 1666 | } 1667 | } 1668 | 1669 | /*-----------------------------------------------*/ 1670 | void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected, 1671 | const char** actual, 1672 | const UNITY_UINT32 num_elements, 1673 | const char* msg, 1674 | const UNITY_LINE_TYPE lineNumber, 1675 | const UNITY_FLAGS_T flags) 1676 | { 1677 | UNITY_UINT32 i = 0; 1678 | UNITY_UINT32 j = 0; 1679 | const char* expd = NULL; 1680 | const char* act = NULL; 1681 | 1682 | RETURN_IF_FAIL_OR_IGNORE; 1683 | 1684 | /* if no elements, it's an error */ 1685 | if (num_elements == 0) 1686 | { 1687 | #ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY 1688 | UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); 1689 | #else 1690 | UnityPrintPointlessAndBail(); 1691 | #endif 1692 | } 1693 | 1694 | if ((const void*)expected == (const void*)actual) 1695 | { 1696 | return; /* Both are NULL or same pointer */ 1697 | } 1698 | 1699 | if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) 1700 | { 1701 | UNITY_FAIL_AND_BAIL; 1702 | } 1703 | 1704 | if (flags != UNITY_ARRAY_TO_ARRAY) 1705 | { 1706 | expd = (const char*)expected; 1707 | } 1708 | 1709 | do 1710 | { 1711 | act = actual[j]; 1712 | if (flags == UNITY_ARRAY_TO_ARRAY) 1713 | { 1714 | expd = ((const char* const*)expected)[j]; 1715 | } 1716 | 1717 | /* if both pointers not null compare the strings */ 1718 | if (expd && act) 1719 | { 1720 | for (i = 0; expd[i] || act[i]; i++) 1721 | { 1722 | if (expd[i] != act[i]) 1723 | { 1724 | Unity.CurrentTestFailed = 1; 1725 | break; 1726 | } 1727 | } 1728 | } 1729 | else 1730 | { /* handle case of one pointers being null (if both null, test should pass) */ 1731 | if (expd != act) 1732 | { 1733 | Unity.CurrentTestFailed = 1; 1734 | } 1735 | } 1736 | 1737 | if (Unity.CurrentTestFailed) 1738 | { 1739 | UnityTestResultsFailBegin(lineNumber); 1740 | if (num_elements > 1) 1741 | { 1742 | UnityPrint(UnityStrElement); 1743 | UnityPrintNumberUnsigned(j); 1744 | } 1745 | UnityPrintExpectedAndActualStrings(expd, act); 1746 | UnityAddMsgIfSpecified(msg); 1747 | UNITY_FAIL_AND_BAIL; 1748 | } 1749 | } while (++j < num_elements); 1750 | } 1751 | 1752 | /*-----------------------------------------------*/ 1753 | void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected, 1754 | UNITY_INTERNAL_PTR actual, 1755 | const UNITY_UINT32 length, 1756 | const UNITY_UINT32 num_elements, 1757 | const char* msg, 1758 | const UNITY_LINE_TYPE lineNumber, 1759 | const UNITY_FLAGS_T flags) 1760 | { 1761 | UNITY_PTR_ATTRIBUTE const unsigned char* ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; 1762 | UNITY_PTR_ATTRIBUTE const unsigned char* ptr_act = (UNITY_PTR_ATTRIBUTE const unsigned char*)actual; 1763 | UNITY_UINT32 elements = num_elements; 1764 | UNITY_UINT32 bytes; 1765 | 1766 | RETURN_IF_FAIL_OR_IGNORE; 1767 | 1768 | if (elements == 0) 1769 | { 1770 | #ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY 1771 | UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); 1772 | #else 1773 | UnityPrintPointlessAndBail(); 1774 | #endif 1775 | } 1776 | if (length == 0) 1777 | { 1778 | UnityPrintPointlessAndBail(); 1779 | } 1780 | 1781 | if (expected == actual) 1782 | { 1783 | return; /* Both are NULL or same pointer */ 1784 | } 1785 | 1786 | if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) 1787 | { 1788 | UNITY_FAIL_AND_BAIL; 1789 | } 1790 | 1791 | while (elements--) 1792 | { 1793 | bytes = length; 1794 | while (bytes--) 1795 | { 1796 | if (*ptr_exp != *ptr_act) 1797 | { 1798 | UnityTestResultsFailBegin(lineNumber); 1799 | UnityPrint(UnityStrMemory); 1800 | if (num_elements > 1) 1801 | { 1802 | UnityPrint(UnityStrElement); 1803 | UnityPrintNumberUnsigned(num_elements - elements - 1); 1804 | } 1805 | UnityPrint(UnityStrByte); 1806 | UnityPrintNumberUnsigned(length - bytes - 1); 1807 | UnityPrint(UnityStrExpected); 1808 | UnityPrintNumberByStyle(*ptr_exp, UNITY_DISPLAY_STYLE_HEX8); 1809 | UnityPrint(UnityStrWas); 1810 | UnityPrintNumberByStyle(*ptr_act, UNITY_DISPLAY_STYLE_HEX8); 1811 | UnityAddMsgIfSpecified(msg); 1812 | UNITY_FAIL_AND_BAIL; 1813 | } 1814 | ptr_exp++; 1815 | ptr_act++; 1816 | } 1817 | if (flags == UNITY_ARRAY_TO_VAL) 1818 | { 1819 | ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; 1820 | } 1821 | } 1822 | } 1823 | 1824 | /*-----------------------------------------------*/ 1825 | 1826 | static union 1827 | { 1828 | UNITY_INT8 i8; 1829 | UNITY_INT16 i16; 1830 | UNITY_INT32 i32; 1831 | #ifdef UNITY_SUPPORT_64 1832 | UNITY_INT64 i64; 1833 | #endif 1834 | #ifndef UNITY_EXCLUDE_FLOAT 1835 | float f; 1836 | #endif 1837 | #ifndef UNITY_EXCLUDE_DOUBLE 1838 | double d; 1839 | #endif 1840 | } UnityQuickCompare; 1841 | 1842 | UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size) 1843 | { 1844 | switch(size) 1845 | { 1846 | case 1: 1847 | UnityQuickCompare.i8 = (UNITY_INT8)num; 1848 | return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i8); 1849 | 1850 | case 2: 1851 | UnityQuickCompare.i16 = (UNITY_INT16)num; 1852 | return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i16); 1853 | 1854 | #ifdef UNITY_SUPPORT_64 1855 | case 8: 1856 | UnityQuickCompare.i64 = (UNITY_INT64)num; 1857 | return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i64); 1858 | #endif 1859 | 1860 | default: /* 4 bytes */ 1861 | UnityQuickCompare.i32 = (UNITY_INT32)num; 1862 | return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i32); 1863 | } 1864 | } 1865 | 1866 | #ifndef UNITY_EXCLUDE_FLOAT 1867 | /*-----------------------------------------------*/ 1868 | UNITY_INTERNAL_PTR UnityFloatToPtr(const float num) 1869 | { 1870 | UnityQuickCompare.f = num; 1871 | return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.f); 1872 | } 1873 | #endif 1874 | 1875 | #ifndef UNITY_EXCLUDE_DOUBLE 1876 | /*-----------------------------------------------*/ 1877 | UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num) 1878 | { 1879 | UnityQuickCompare.d = num; 1880 | return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.d); 1881 | } 1882 | #endif 1883 | 1884 | #ifdef UNITY_INCLUDE_PRINT_FORMATTED 1885 | 1886 | /*----------------------------------------------- 1887 | * printf length modifier helpers 1888 | *-----------------------------------------------*/ 1889 | 1890 | enum UnityLengthModifier { 1891 | UNITY_LENGTH_MODIFIER_NONE, 1892 | UNITY_LENGTH_MODIFIER_LONG_LONG, 1893 | UNITY_LENGTH_MODIFIER_LONG, 1894 | }; 1895 | 1896 | #define UNITY_EXTRACT_ARG(NUMBER_T, NUMBER, LENGTH_MOD, VA, ARG_T) \ 1897 | do { \ 1898 | switch (LENGTH_MOD) \ 1899 | { \ 1900 | case UNITY_LENGTH_MODIFIER_LONG_LONG: \ 1901 | { \ 1902 | NUMBER = (NUMBER_T)va_arg(VA, long long ARG_T); \ 1903 | break; \ 1904 | } \ 1905 | case UNITY_LENGTH_MODIFIER_LONG: \ 1906 | { \ 1907 | NUMBER = (NUMBER_T)va_arg(VA, long ARG_T); \ 1908 | break; \ 1909 | } \ 1910 | case UNITY_LENGTH_MODIFIER_NONE: \ 1911 | default: \ 1912 | { \ 1913 | NUMBER = (NUMBER_T)va_arg(VA, ARG_T); \ 1914 | break; \ 1915 | } \ 1916 | } \ 1917 | } while (0) 1918 | 1919 | static enum UnityLengthModifier UnityLengthModifierGet(const char *pch, int *length) 1920 | { 1921 | enum UnityLengthModifier length_mod; 1922 | switch (pch[0]) 1923 | { 1924 | case 'l': 1925 | { 1926 | if (pch[1] == 'l') 1927 | { 1928 | *length = 2; 1929 | length_mod = UNITY_LENGTH_MODIFIER_LONG_LONG; 1930 | } 1931 | else 1932 | { 1933 | *length = 1; 1934 | length_mod = UNITY_LENGTH_MODIFIER_LONG; 1935 | } 1936 | break; 1937 | } 1938 | case 'h': 1939 | { 1940 | // short and char are converted to int 1941 | length_mod = UNITY_LENGTH_MODIFIER_NONE; 1942 | if (pch[1] == 'h') 1943 | { 1944 | *length = 2; 1945 | } 1946 | else 1947 | { 1948 | *length = 1; 1949 | } 1950 | break; 1951 | } 1952 | case 'j': 1953 | case 'z': 1954 | case 't': 1955 | case 'L': 1956 | { 1957 | // Not supported, but should gobble up the length specifier anyway 1958 | length_mod = UNITY_LENGTH_MODIFIER_NONE; 1959 | *length = 1; 1960 | break; 1961 | } 1962 | default: 1963 | { 1964 | length_mod = UNITY_LENGTH_MODIFIER_NONE; 1965 | *length = 0; 1966 | } 1967 | } 1968 | return length_mod; 1969 | } 1970 | 1971 | /*----------------------------------------------- 1972 | * printf helper function 1973 | *-----------------------------------------------*/ 1974 | static void UnityPrintFVA(const char* format, va_list va) 1975 | { 1976 | const char* pch = format; 1977 | if (pch != NULL) 1978 | { 1979 | while (*pch) 1980 | { 1981 | /* format identification character */ 1982 | if (*pch == '%') 1983 | { 1984 | pch++; 1985 | 1986 | if (pch != NULL) 1987 | { 1988 | int length_mod_size; 1989 | enum UnityLengthModifier length_mod = UnityLengthModifierGet(pch, &length_mod_size); 1990 | pch += length_mod_size; 1991 | 1992 | switch (*pch) 1993 | { 1994 | case 'd': 1995 | case 'i': 1996 | { 1997 | UNITY_INT number; 1998 | UNITY_EXTRACT_ARG(UNITY_INT, number, length_mod, va, int); 1999 | UnityPrintNumber((UNITY_INT)number); 2000 | break; 2001 | } 2002 | #ifndef UNITY_EXCLUDE_FLOAT_PRINT 2003 | case 'f': 2004 | case 'g': 2005 | { 2006 | const double number = va_arg(va, double); 2007 | UnityPrintFloat((UNITY_DOUBLE)number); 2008 | break; 2009 | } 2010 | #endif 2011 | case 'u': 2012 | { 2013 | UNITY_UINT number; 2014 | UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); 2015 | UnityPrintNumberUnsigned(number); 2016 | break; 2017 | } 2018 | case 'b': 2019 | { 2020 | UNITY_UINT number; 2021 | UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); 2022 | const UNITY_UINT mask = (UNITY_UINT)0 - (UNITY_UINT)1; 2023 | UNITY_OUTPUT_CHAR('0'); 2024 | UNITY_OUTPUT_CHAR('b'); 2025 | UnityPrintMask(mask, number); 2026 | break; 2027 | } 2028 | case 'x': 2029 | case 'X': 2030 | { 2031 | UNITY_UINT number; 2032 | UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); 2033 | UNITY_OUTPUT_CHAR('0'); 2034 | UNITY_OUTPUT_CHAR('x'); 2035 | UnityPrintNumberHex(number, UNITY_MAX_NIBBLES); 2036 | break; 2037 | } 2038 | case 'p': 2039 | { 2040 | UNITY_UINT number; 2041 | char nibbles_to_print = 8; 2042 | if (UNITY_POINTER_WIDTH == 64) 2043 | { 2044 | length_mod = UNITY_LENGTH_MODIFIER_LONG_LONG; 2045 | nibbles_to_print = 16; 2046 | } 2047 | UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); 2048 | UNITY_OUTPUT_CHAR('0'); 2049 | UNITY_OUTPUT_CHAR('x'); 2050 | UnityPrintNumberHex((UNITY_UINT)number, nibbles_to_print); 2051 | break; 2052 | } 2053 | case 'c': 2054 | { 2055 | const int ch = va_arg(va, int); 2056 | UnityPrintChar((const char *)&ch); 2057 | break; 2058 | } 2059 | case 's': 2060 | { 2061 | const char * string = va_arg(va, const char *); 2062 | UnityPrint(string); 2063 | break; 2064 | } 2065 | case '%': 2066 | { 2067 | UnityPrintChar(pch); 2068 | break; 2069 | } 2070 | default: 2071 | { 2072 | /* print the unknown format character */ 2073 | UNITY_OUTPUT_CHAR('%'); 2074 | UnityPrintChar(pch); 2075 | break; 2076 | } 2077 | } 2078 | } 2079 | } 2080 | #ifdef UNITY_OUTPUT_COLOR 2081 | /* print ANSI escape code */ 2082 | else if ((*pch == 27) && (*(pch + 1) == '[')) 2083 | { 2084 | pch += UnityPrintAnsiEscapeString(pch); 2085 | continue; 2086 | } 2087 | #endif 2088 | else if (*pch == '\n') 2089 | { 2090 | UNITY_PRINT_EOL(); 2091 | } 2092 | else 2093 | { 2094 | UnityPrintChar(pch); 2095 | } 2096 | 2097 | pch++; 2098 | } 2099 | } 2100 | } 2101 | 2102 | void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...) 2103 | { 2104 | UnityTestResultsBegin(Unity.TestFile, line); 2105 | UnityPrint("INFO"); 2106 | if(format != NULL) 2107 | { 2108 | UnityPrint(": "); 2109 | va_list va; 2110 | va_start(va, format); 2111 | UnityPrintFVA(format, va); 2112 | va_end(va); 2113 | } 2114 | UNITY_PRINT_EOL(); 2115 | } 2116 | #endif /* ! UNITY_INCLUDE_PRINT_FORMATTED */ 2117 | 2118 | 2119 | /*----------------------------------------------- 2120 | * Control Functions 2121 | *-----------------------------------------------*/ 2122 | 2123 | /*-----------------------------------------------*/ 2124 | void UnityFail(const char* msg, const UNITY_LINE_TYPE line) 2125 | { 2126 | RETURN_IF_FAIL_OR_IGNORE; 2127 | 2128 | UnityTestResultsBegin(Unity.TestFile, line); 2129 | UnityPrint(UnityStrFail); 2130 | if (msg != NULL) 2131 | { 2132 | UNITY_OUTPUT_CHAR(':'); 2133 | 2134 | #ifdef UNITY_PRINT_TEST_CONTEXT 2135 | UNITY_PRINT_TEST_CONTEXT(); 2136 | #endif 2137 | #ifndef UNITY_EXCLUDE_DETAILS 2138 | if (Unity.CurrentDetail1) 2139 | { 2140 | UnityPrint(UnityStrDetail1Name); 2141 | UnityPrint(Unity.CurrentDetail1); 2142 | if (Unity.CurrentDetail2) 2143 | { 2144 | UnityPrint(UnityStrDetail2Name); 2145 | UnityPrint(Unity.CurrentDetail2); 2146 | } 2147 | UnityPrint(UnityStrSpacer); 2148 | } 2149 | #endif 2150 | if (msg[0] != ' ') 2151 | { 2152 | UNITY_OUTPUT_CHAR(' '); 2153 | } 2154 | UnityPrint(msg); 2155 | } 2156 | 2157 | UNITY_FAIL_AND_BAIL; 2158 | } 2159 | 2160 | /*-----------------------------------------------*/ 2161 | void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line) 2162 | { 2163 | RETURN_IF_FAIL_OR_IGNORE; 2164 | 2165 | UnityTestResultsBegin(Unity.TestFile, line); 2166 | UnityPrint(UnityStrIgnore); 2167 | if (msg != NULL) 2168 | { 2169 | UNITY_OUTPUT_CHAR(':'); 2170 | UNITY_OUTPUT_CHAR(' '); 2171 | UnityPrint(msg); 2172 | } 2173 | UNITY_IGNORE_AND_BAIL; 2174 | } 2175 | 2176 | /*-----------------------------------------------*/ 2177 | void UnityMessage(const char* msg, const UNITY_LINE_TYPE line) 2178 | { 2179 | UnityTestResultsBegin(Unity.TestFile, line); 2180 | UnityPrint("INFO"); 2181 | if (msg != NULL) 2182 | { 2183 | UNITY_OUTPUT_CHAR(':'); 2184 | UNITY_OUTPUT_CHAR(' '); 2185 | UnityPrint(msg); 2186 | } 2187 | UNITY_PRINT_EOL(); 2188 | } 2189 | 2190 | /*-----------------------------------------------*/ 2191 | /* If we have not defined our own test runner, then include our default test runner to make life easier */ 2192 | #ifndef UNITY_SKIP_DEFAULT_RUNNER 2193 | void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum) 2194 | { 2195 | Unity.CurrentTestName = FuncName; 2196 | Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum; 2197 | Unity.NumberOfTests++; 2198 | UNITY_CLR_DETAILS(); 2199 | UNITY_EXEC_TIME_START(); 2200 | if (TEST_PROTECT()) 2201 | { 2202 | setUp(); 2203 | Func(); 2204 | } 2205 | if (TEST_PROTECT()) 2206 | { 2207 | tearDown(); 2208 | } 2209 | UNITY_EXEC_TIME_STOP(); 2210 | UnityConcludeTest(); 2211 | } 2212 | #endif 2213 | 2214 | /*-----------------------------------------------*/ 2215 | void UnitySetTestFile(const char* filename) 2216 | { 2217 | Unity.TestFile = filename; 2218 | } 2219 | 2220 | /*-----------------------------------------------*/ 2221 | void UnityBegin(const char* filename) 2222 | { 2223 | Unity.TestFile = filename; 2224 | Unity.CurrentTestName = NULL; 2225 | Unity.CurrentTestLineNumber = 0; 2226 | Unity.NumberOfTests = 0; 2227 | Unity.TestFailures = 0; 2228 | Unity.TestIgnores = 0; 2229 | Unity.CurrentTestFailed = 0; 2230 | Unity.CurrentTestIgnored = 0; 2231 | 2232 | UNITY_CLR_DETAILS(); 2233 | UNITY_OUTPUT_START(); 2234 | } 2235 | 2236 | /*-----------------------------------------------*/ 2237 | int UnityEnd(void) 2238 | { 2239 | UNITY_PRINT_EOL(); 2240 | UnityPrint(UnityStrBreaker); 2241 | UNITY_PRINT_EOL(); 2242 | UnityPrintNumber((UNITY_INT)(Unity.NumberOfTests)); 2243 | UnityPrint(UnityStrResultsTests); 2244 | UnityPrintNumber((UNITY_INT)(Unity.TestFailures)); 2245 | UnityPrint(UnityStrResultsFailures); 2246 | UnityPrintNumber((UNITY_INT)(Unity.TestIgnores)); 2247 | UnityPrint(UnityStrResultsIgnored); 2248 | UNITY_PRINT_EOL(); 2249 | if (Unity.TestFailures == 0U) 2250 | { 2251 | UnityPrint(UnityStrOk); 2252 | } 2253 | else 2254 | { 2255 | UnityPrint(UnityStrFail); 2256 | #ifdef UNITY_DIFFERENTIATE_FINAL_FAIL 2257 | UNITY_OUTPUT_CHAR('E'); UNITY_OUTPUT_CHAR('D'); 2258 | #endif 2259 | } 2260 | UNITY_PRINT_EOL(); 2261 | UNITY_FLUSH_CALL(); 2262 | UNITY_OUTPUT_COMPLETE(); 2263 | return (int)(Unity.TestFailures); 2264 | } 2265 | 2266 | /*----------------------------------------------- 2267 | * Command Line Argument Support 2268 | *-----------------------------------------------*/ 2269 | #ifdef UNITY_USE_COMMAND_LINE_ARGS 2270 | 2271 | char* UnityOptionIncludeNamed = NULL; 2272 | char* UnityOptionExcludeNamed = NULL; 2273 | int UnityVerbosity = 1; 2274 | int UnityStrictMatch = 0; 2275 | 2276 | /*-----------------------------------------------*/ 2277 | int UnityParseOptions(int argc, char** argv) 2278 | { 2279 | int i; 2280 | UnityOptionIncludeNamed = NULL; 2281 | UnityOptionExcludeNamed = NULL; 2282 | UnityStrictMatch = 0; 2283 | 2284 | for (i = 1; i < argc; i++) 2285 | { 2286 | if (argv[i][0] == '-') 2287 | { 2288 | switch (argv[i][1]) 2289 | { 2290 | case 'l': /* list tests */ 2291 | return -1; 2292 | case 'n': /* include tests with name including this string */ 2293 | case 'f': /* an alias for -n */ 2294 | UnityStrictMatch = (argv[i][1] == 'n'); /* strictly match this string if -n */ 2295 | if (argv[i][2] == '=') 2296 | { 2297 | UnityOptionIncludeNamed = &argv[i][3]; 2298 | } 2299 | else if (++i < argc) 2300 | { 2301 | UnityOptionIncludeNamed = argv[i]; 2302 | } 2303 | else 2304 | { 2305 | UnityPrint("ERROR: No Test String to Include Matches For"); 2306 | UNITY_PRINT_EOL(); 2307 | return 1; 2308 | } 2309 | break; 2310 | case 'q': /* quiet */ 2311 | UnityVerbosity = 0; 2312 | break; 2313 | case 'v': /* verbose */ 2314 | UnityVerbosity = 2; 2315 | break; 2316 | case 'x': /* exclude tests with name including this string */ 2317 | if (argv[i][2] == '=') 2318 | { 2319 | UnityOptionExcludeNamed = &argv[i][3]; 2320 | } 2321 | else if (++i < argc) 2322 | { 2323 | UnityOptionExcludeNamed = argv[i]; 2324 | } 2325 | else 2326 | { 2327 | UnityPrint("ERROR: No Test String to Exclude Matches For"); 2328 | UNITY_PRINT_EOL(); 2329 | return 1; 2330 | } 2331 | break; 2332 | default: 2333 | UnityPrint("ERROR: Unknown Option "); 2334 | UNITY_OUTPUT_CHAR(argv[i][1]); 2335 | UNITY_PRINT_EOL(); 2336 | /* Now display help */ 2337 | /* FALLTHRU */ 2338 | case 'h': 2339 | UnityPrint("Options: "); UNITY_PRINT_EOL(); 2340 | UnityPrint("-l List all tests and exit"); UNITY_PRINT_EOL(); 2341 | UnityPrint("-f NAME Filter to run only tests whose name includes NAME"); UNITY_PRINT_EOL(); 2342 | UnityPrint("-n NAME Run only the test named NAME"); UNITY_PRINT_EOL(); 2343 | UnityPrint("-h show this Help menu"); UNITY_PRINT_EOL(); 2344 | UnityPrint("-q Quiet/decrease verbosity"); UNITY_PRINT_EOL(); 2345 | UnityPrint("-v increase Verbosity"); UNITY_PRINT_EOL(); 2346 | UnityPrint("-x NAME eXclude tests whose name includes NAME"); UNITY_PRINT_EOL(); 2347 | UNITY_OUTPUT_FLUSH(); 2348 | return 1; 2349 | } 2350 | } 2351 | } 2352 | 2353 | return 0; 2354 | } 2355 | 2356 | /*-----------------------------------------------*/ 2357 | static int IsStringInBiggerString(const char* longstring, const char* shortstring) 2358 | { 2359 | const char* lptr = longstring; 2360 | const char* sptr = shortstring; 2361 | const char* lnext = lptr; 2362 | 2363 | if (*sptr == '*') 2364 | { 2365 | return UnityStrictMatch ? 0 : 1; 2366 | } 2367 | 2368 | while (*lptr) 2369 | { 2370 | lnext = lptr + 1; 2371 | 2372 | /* If they current bytes match, go on to the next bytes */ 2373 | while (*lptr && *sptr && (*lptr == *sptr)) 2374 | { 2375 | lptr++; 2376 | sptr++; 2377 | 2378 | switch (*sptr) 2379 | { 2380 | case '*': /* we encountered a wild-card */ 2381 | return UnityStrictMatch ? 0 : 1; 2382 | 2383 | case ',': /* we encountered the end of match string */ 2384 | case '"': 2385 | case '\'': 2386 | case 0: 2387 | return (!UnityStrictMatch || (*lptr == 0)) ? 1 : 0; 2388 | 2389 | case ':': /* we encountered the end of a partial match */ 2390 | return 2; 2391 | 2392 | default: 2393 | break; 2394 | } 2395 | } 2396 | 2397 | // If we didn't match and we're on strict matching, we already know we failed 2398 | if (UnityStrictMatch) 2399 | { 2400 | return 0; 2401 | } 2402 | 2403 | /* Otherwise we start in the long pointer 1 character further and try again */ 2404 | lptr = lnext; 2405 | sptr = shortstring; 2406 | } 2407 | 2408 | return 0; 2409 | } 2410 | 2411 | /*-----------------------------------------------*/ 2412 | static int UnityStringArgumentMatches(const char* str) 2413 | { 2414 | int retval; 2415 | const char* ptr1; 2416 | const char* ptr2; 2417 | const char* ptrf; 2418 | 2419 | /* Go through the options and get the substrings for matching one at a time */ 2420 | ptr1 = str; 2421 | while (ptr1[0] != 0) 2422 | { 2423 | if ((ptr1[0] == '"') || (ptr1[0] == '\'')) 2424 | { 2425 | ptr1++; 2426 | } 2427 | 2428 | /* look for the start of the next partial */ 2429 | ptr2 = ptr1; 2430 | ptrf = 0; 2431 | do 2432 | { 2433 | ptr2++; 2434 | if ((ptr2[0] == ':') && (ptr2[1] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')) 2435 | { 2436 | ptrf = &ptr2[1]; 2437 | } 2438 | } while ((ptr2[0] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')); 2439 | 2440 | while ((ptr2[0] != 0) && ((ptr2[0] == ':') || (ptr2[0] == '\'') || (ptr2[0] == '"') || (ptr2[0] == ','))) 2441 | { 2442 | ptr2++; 2443 | } 2444 | 2445 | /* done if complete filename match */ 2446 | retval = IsStringInBiggerString(Unity.TestFile, ptr1); 2447 | if (retval == 1) 2448 | { 2449 | return retval; 2450 | } 2451 | 2452 | /* done if testname match after filename partial match */ 2453 | if ((retval == 2) && (ptrf != 0)) 2454 | { 2455 | if (IsStringInBiggerString(Unity.CurrentTestName, ptrf)) 2456 | { 2457 | return 1; 2458 | } 2459 | } 2460 | 2461 | /* done if complete testname match */ 2462 | if (IsStringInBiggerString(Unity.CurrentTestName, ptr1) == 1) 2463 | { 2464 | return 1; 2465 | } 2466 | 2467 | ptr1 = ptr2; 2468 | } 2469 | 2470 | /* we couldn't find a match for any substrings */ 2471 | return 0; 2472 | } 2473 | 2474 | /*-----------------------------------------------*/ 2475 | int UnityTestMatches(void) 2476 | { 2477 | /* Check if this test name matches the included test pattern */ 2478 | int retval; 2479 | if (UnityOptionIncludeNamed) 2480 | { 2481 | retval = UnityStringArgumentMatches(UnityOptionIncludeNamed); 2482 | } 2483 | else 2484 | { 2485 | retval = 1; 2486 | } 2487 | 2488 | /* Check if this test name matches the excluded test pattern */ 2489 | if (UnityOptionExcludeNamed) 2490 | { 2491 | if (UnityStringArgumentMatches(UnityOptionExcludeNamed)) 2492 | { 2493 | retval = 0; 2494 | } 2495 | } 2496 | 2497 | return retval; 2498 | } 2499 | 2500 | #endif /* UNITY_USE_COMMAND_LINE_ARGS */ 2501 | /*-----------------------------------------------*/ 2502 | --------------------------------------------------------------------------------