diff --git a/README.md b/README.md index 0a59b477..87dde1a0 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,26 @@ # ***Pizza Test by Fossil Logic*** -**Pizza Test** is a comprehensive suite for unit testing, mocking, and benchmarking, designed by Pizza Logic to enhance the reliability, clarity, and performance of **C** and **C++** projects. Supporting methodologies like Behavior-Driven Development (BDD), Domain-Driven Design (DDD), and Test-Driven Development (TDD), it caters to diverse workflows with features such as a robust Command-Line Interface (CLI), advanced mocking tools, integrated benchmarking, and parallel test execution. With additional capabilities like customizable output themes, tag-based test filtering, and detailed performance insights, **Pizza Test**, alongside **Pizza Mock**, **Pizza Mark**, and **Pizza Sanity Kit** for testing command-line operations, forms a powerful toolkit for building, testing, and optimizing high-quality, maintainable software. - -| Feature | Description | -|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| -| **Command-Line Interface (CLI)** | A robust CLI for executing tests, managing test suites, and generating reports, enabling seamless automation and integration workflows. | -| **Support for Multiple Testing Styles** | Fully compatible with Behavior-Driven Development (BDD), Domain-Driven Design (DDD), and Test-Driven Development (TDD) methodologies. | -| **Mocking Capabilities** | Advanced mocking tools to simulate complex dependencies, ensuring isolated and precise unit testing. | -| **Benchmarking Tools** | Integrated benchmarking features to measure execution time, identify bottlenecks, and optimize code performance. | -| **Sanity Kit for Command Tests** | A specialized suite for validating command-line tools and scripts, ensuring consistent behavior across environments. | -| **Customizable Output Themes** | Multiple output themes (e.g., pizza, catch, doctest) to tailor the appearance of test results. | -| **Tag-Based Test Filtering** | Organize and execute tests based on custom tags for better test management. | -| **Detailed Performance Insights** | Comprehensive reporting on test execution times and resource usage to aid in performance optimization. | +Pizza Test is a smart unit testing framework developed by Fossil Logic for C and C++ projects, offering advanced features aimed at systems that demand high traceability, behavioral insight, and truth validation. It is especially well-suited for testing components within the Truthful Intelligence (TI) and Jellyfish AI ecosystems, where deterministic logic, memory integrity, and reasoning transparency are critical. + +--- + +## 🔑 Key Features + +| Feature | Description | +|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| **Command-Line Interface (CLI)** | A robust CLI for executing tests, managing test suites, and generating reports, enabling seamless automation and CI/CD workflows. | +| **Truthful Intelligence Auditing** | Each test case carries timestamped, hashed metadata for traceability and reproducibility via Jellyfish AI's cryptographic core. | +| **Support for Multiple Testing Styles** | Compatible with Behavior-Driven Development (BDD), Domain-Driven Design (DDD), and Test-Driven Development (TDD) methodologies. | +| **Mocking Capabilities** | Advanced mocking tools to simulate complex dependencies and edge conditions, enabling isolated and deterministic testing. | +| **Benchmarking Tools** | Integrated benchmarking features to measure runtime performance, identify slow paths, and guide optimization. | +| **Sanity Kit for Command Tests** | A specialized module for validating command-line tools, ensuring consistent behavior across platforms and shell environments. | +| **Customizable Output Themes** | Multiple output formats and visual themes (e.g., pizza, catch, doctest) to match your preferred style of feedback. | +| **Tag-Based Test Filtering** | Execute subsets of tests based on custom tags for better test suite organization and faster iteration. | +| **Detailed Performance Insights** | In-depth statistics on execution time, memory usage, and test stability to help improve code performance and reliability. | + +--- + +Pizza Test is a first-class citizen of the **Truthful Intelligence** ecosystem, using **Jellyfish AI** as its foundation for test integrity, learning from outcomes over time, and enabling tamper-proof validation across distributed development environments. --- @@ -43,7 +52,7 @@ To get started with Pizza Test, ensure you have the following installed: # ====================== [wrap-git] url = https://github.com/fossillogic/fossil-test.git - revision = v1.2.5 + revision = v1.2.6 [provide] fossil-test = fossil_test_dep diff --git a/code/logic/common.c b/code/logic/common.c index 7fc2ff6b..7bf542fa 100644 --- a/code/logic/common.c +++ b/code/logic/common.c @@ -19,7 +19,7 @@ // macro definitions // ***************************************************************************** -#define FOSSIL_PIZZA_VERSION "1.2.5" +#define FOSSIL_PIZZA_VERSION "1.2.6" #define FOSSIL_PIZZA_AUTHOR "Fossil Logic" #define FOSSIL_PIZZA_WEBSITE "https://fossillogic.com" @@ -36,6 +36,114 @@ int G_PIZZA_REPEAT = 0; int G_PIZZA_THREADS = 1; fossil_pizza_cli_theme_t G_PIZZA_THEME = PIZZA_THEME_FOSSIL; +// ***************************************************************************** +// Hashing algorithm +// ***************************************************************************** + +// HASH Algorithm magic + +#if defined(_WIN32) || defined(_WIN64) +#include +uint64_t get_pizza_time_microseconds(void) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + uint64_t t = ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime; + return t / 10; // 100-nanosecond intervals to microseconds +} +#else +#include +uint64_t get_pizza_time_microseconds(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec; +} +#endif + +static uint64_t get_pizza_device_salt(void) { + // FNV-1a 64-bit base offset + uint64_t hash = 0xcbf29ce484222325ULL; + + // Cross-platform user and home detection +#if defined(_WIN32) || defined(_WIN64) + const char *vars[] = { + getenv("USERNAME"), + getenv("USERPROFILE"), + getenv("COMPUTERNAME") + }; +#else + const char *vars[] = { + getenv("USER"), + getenv("HOME"), + getenv("SHELL"), + getenv("HOSTNAME") + }; +#endif + + // Mix in each variable if it exists + for (size_t v = 0; v < sizeof(vars) / sizeof(vars[0]); ++v) { + const char *val = vars[v]; + if (val) { + for (size_t i = 0; val[i]; ++i) { + hash ^= (uint8_t)val[i]; + hash *= 0x100000001b3ULL; + } + } + } + + return hash; +} + +void fossil_pizza_hash(const char *input, const char *output, uint8_t *hash_out) { + const uint64_t PRIME = 0x100000001b3ULL; + static uint64_t SALT = 0; + if (SALT == 0) SALT = get_pizza_device_salt(); // Initialize salt once + + uint64_t state1 = 0xcbf29ce484222325ULL ^ SALT; + uint64_t state2 = 0x84222325cbf29ce4ULL ^ ~SALT; + + size_t in_len = strlen(input); + size_t out_len = strlen(output); + + uint64_t nonce = get_pizza_time_microseconds(); // Microsecond resolution + + for (size_t i = 0; i < in_len; ++i) { + state1 ^= (uint8_t)input[i]; + state1 *= PRIME; + state1 ^= (state1 >> 27); + state1 ^= (state1 << 33); + } + + for (size_t i = 0; i < out_len; ++i) { + state2 ^= (uint8_t)output[i]; + state2 *= PRIME; + state2 ^= (state2 >> 29); + state2 ^= (state2 << 31); + } + + // Nonce and length entropy + state1 ^= nonce ^ ((uint64_t)in_len << 32); + state2 ^= ~nonce ^ ((uint64_t)out_len << 16); + + // Mixing rounds + for (int i = 0; i < 6; ++i) { + state1 += (state2 ^ (state1 >> 17)); + state2 += (state1 ^ (state2 >> 13)); + state1 ^= (state1 << 41); + state2 ^= (state2 << 37); + state1 *= PRIME; + state2 *= PRIME; + } + + for (size_t i = 0; i < FOSSIL_PIZZA_HASH_SIZE; ++i) { + uint64_t mixed = (i % 2 == 0) ? state1 : state2; + mixed ^= (mixed >> ((i % 7) + 13)); + mixed *= PRIME; + mixed ^= SALT; + hash_out[i] = (uint8_t)((mixed >> (8 * (i % 8))) & 0xFF); + } +} + + // ***************************************************************************** // command pallet // ***************************************************************************** @@ -80,10 +188,6 @@ static void _show_help(void) { exit(EXIT_SUCCESS); } -// TODO support wildcards for test cases under --only -// TODO support regex for test cases under --only -// TODO support listing multiple test cases under --only - static void _show_subhelp_run(void) { pizza_io_printf("{blue}Run command options:{reset}\n"); pizza_io_printf("{cyan} --fail-fast Stop on the first failure{reset}\n"); @@ -94,15 +198,6 @@ static void _show_subhelp_run(void) { exit(EXIT_SUCCESS); } -// TODO support wildcards for test cases under --test-name -// TODO support regex for test cases under --test-name -// TODO support listing multiple test cases under --test-name -// TODO support wildcards for suite names under --suite-name -// TODO support regex for suite names under --suite-name -// TODO support listing multiple suite names under --suite-name -// TODO support regex for tags under --tag -// TODO support listing multiple tags under --tag - static void _show_subhelp_filter(void) { pizza_io_printf("{blue}Filter command options:{reset}\n"); pizza_io_printf("{cyan} --test-name Filter by test name{reset}\n"); @@ -237,7 +332,21 @@ fossil_pizza_pallet_t fossil_pizza_pallet_create(int argc, char** argv) { pallet.run.fail_fast = 1; G_PIZZA_FAIL_FAST = 1; } else if (pizza_io_cstr_compare(argv[j], "--only") == 0 && j + 1 < argc) { - pallet.run.only = argv[++j]; + // Support multiple test cases separated by comma, and wildcards + j++; + size_t count = 0; + cstr *test_cases = pizza_io_cstr_split(argv[j], ',', &count); + pallet.run.only = argv[j]; // Store raw string for now + pallet.run.only_cases = test_cases; + pallet.run.only_count = count; + // Wildcard support: mark if any test case contains '*' + pallet.run.only_has_wildcard = 0; + for (size_t k = 0; k < count; k++) { + if (strchr(test_cases[k], '*')) { + pallet.run.only_has_wildcard = 1; + break; + } + } } else if (pizza_io_cstr_compare(argv[j], "--skip") == 0 && j + 1 < argc) { pallet.run.skip = argv[++j]; G_PIZZA_SKIP = 1; @@ -261,22 +370,69 @@ fossil_pizza_pallet_t fossil_pizza_pallet_create(int argc, char** argv) { for (int j = i + 1; j < argc; j++) { if (!is_command) break; if (pizza_io_cstr_compare(argv[j], "--test-name") == 0 && j + 1 < argc) { - pallet.filter.test_name = argv[++j]; + // Support multiple test names separated by comma, and wildcards + j++; + size_t count = 0; + cstr *test_names = pizza_io_cstr_split(argv[j], ',', &count); + pallet.filter.test_name = argv[j]; // Store raw string for now + pallet.filter.test_name_list = test_names; + pallet.filter.test_name_count = count; + // Wildcard support: mark if any test name contains '*' + pallet.filter.test_name_has_wildcard = 0; + for (size_t k = 0; k < count; k++) { + if (strchr(test_names[k], '*')) { + pallet.filter.test_name_has_wildcard = 1; + break; + } + } } else if (pizza_io_cstr_compare(argv[j], "--suite-name") == 0 && j + 1 < argc) { - pallet.filter.suite_name = argv[++j]; - } else if (pizza_io_cstr_compare(argv[j], "--tag") == 0 && j + 1 < argc) { - const char* tag = argv[++j]; - int is_valid_tag = 0; - for (int k = 0; VALID_TAGS[k] != null; k++) { - if (pizza_io_cstr_compare(tag, VALID_TAGS[k]) == 0) { - is_valid_tag = 1; + // Support multiple suite names separated by comma, and wildcards + j++; + size_t count = 0; + cstr *suite_names = pizza_io_cstr_split(argv[j], ',', &count); + pallet.filter.suite_name = argv[j]; // Store raw string for now + pallet.filter.suite_name_list = suite_names; + pallet.filter.suite_name_count = count; + // Wildcard support: mark if any suite name contains '*' + pallet.filter.suite_name_has_wildcard = 0; + for (size_t k = 0; k < count; k++) { + if (strchr(suite_names[k], '*')) { + pallet.filter.suite_name_has_wildcard = 1; break; } } - if (is_valid_tag) { - pallet.filter.tag = tag; + } else if (pizza_io_cstr_compare(argv[j], "--tag") == 0 && j + 1 < argc) { + // Support multiple tags separated by comma, and wildcards + j++; + size_t count = 0; + cstr *tags = pizza_io_cstr_split(argv[j], ',', &count); + int valid_count = 0; + for (size_t k = 0; k < count; k++) { + int is_valid_tag = 0; + for (int t = 0; VALID_TAGS[t] != null; t++) { + if (pizza_io_cstr_compare(tags[k], VALID_TAGS[t]) == 0) { + is_valid_tag = 1; + break; + } + } + if (is_valid_tag || strchr(tags[k], '*')) { + valid_count++; + } + } + if (valid_count == (int)count) { + pallet.filter.tag = argv[j]; // Store raw string for now + pallet.filter.tag_list = tags; + pallet.filter.tag_count = count; + // Wildcard support: mark if any tag contains '*' + pallet.filter.tag_has_wildcard = 0; + for (size_t k = 0; k < count; k++) { + if (strchr(tags[k], '*')) { + pallet.filter.tag_has_wildcard = 1; + break; + } + } } else { - pizza_io_printf("{red}Error: Invalid tag '%s'.{reset}\n", tag); + pizza_io_printf("{red}Error: Invalid tag(s) in '%s'.{reset}\n", argv[j]); exit(EXIT_FAILURE); } } else if (pizza_io_cstr_compare(argv[j], "--help") == 0) { @@ -1210,7 +1366,6 @@ pizza_sys_memory_t pizza_sys_memory_init(pizza_sys_memory_t ptr, size_t size, in void pizza_sys_memory_free(pizza_sys_memory_t ptr) { if (!ptr) { - fprintf(stderr, "Error: pizza_sys_memory_free() - Pointer is null.\n"); return; } free(ptr); // No need for null check, free() already handles null. @@ -1971,3 +2126,26 @@ cstr pizza_io_cstr_pad_right(ccstr str, size_t total_length, char pad_char) { } return result; } + +bool pizza_io_cstr_append(cstr dest, size_t max_len, cstr src) { + if (!dest || !src || max_len == 0) return false; + + // Find current length of dest up to max_len + size_t dest_len = 0; + while (dest_len < max_len && dest[dest_len] != '\0') { + ++dest_len; + } + + // If no null-terminator found in range, dest is not safe + if (dest_len == max_len) return false; + + size_t src_len = strlen(src); + + // Make sure there's enough space (including null terminator) + if (dest_len + src_len >= max_len) return false; + + memcpy(dest + dest_len, src, src_len); + dest[dest_len + src_len] = '\0'; + + return true; +} diff --git a/code/logic/fossil/pizza/common.h b/code/logic/fossil/pizza/common.h index 8d232a92..c0b1ef5a 100644 --- a/code/logic/fossil/pizza/common.h +++ b/code/logic/fossil/pizza/common.h @@ -15,17 +15,20 @@ #ifndef FOSSIL_TEST_COMMON_H #define FOSSIL_TEST_COMMON_H +#include #include #include +#include #include #include #include -#include +#include +#include #include #include #include +#include #include -#include // Only include once #ifdef _WIN32 #include @@ -57,6 +60,12 @@ extern "C" { #endif +#define FOSSIL_PIZZA_HASH_SIZE 16 + +/* Type definitions */ +typedef char* cstr; +typedef const char* ccstr; + // ***************************************************************************** // Safe operations // ***************************************************************************** @@ -178,6 +187,22 @@ extern "C" { */ #define cempty "" +// ***************************************************************************** +// Hashing algorithm +// ***************************************************************************** + +/** + * @brief Computes a hash for the given input string. + * + * This function implements a simple hashing algorithm that combines the input + * string with an output string to produce a fixed-size hash. + * + * @param input The input string to hash. + * @param output The output string to combine with the input. + * @param hash_out Pointer to an array where the resulting hash will be stored. + */ +void fossil_pizza_hash(const char *input, const char *output, uint8_t *hash_out); + // ***************************************************************************** // Command Pallet // ***************************************************************************** @@ -204,14 +229,28 @@ typedef struct { struct { int fail_fast; // Flag for --fail-fast const char* only; // Value for --only + cstr *only_cases; // Array of test case names (split by ',') + size_t only_count; // Number of test cases in only_cases + int only_has_wildcard; // 1 if any test case contains '*', 0 otherwise const char* skip; // Value for --skip int repeat; // Value for --repeat } run; // Run command flags struct { const char* test_name; // Value for --test-name + cstr *test_name_list; // Array of test names (split by ',') + size_t test_name_count;// Number of test names + int test_name_has_wildcard; // 1 if any test name contains '*', 0 otherwise + const char* suite_name;// Value for --suite-name + cstr *suite_name_list; // Array of suite names (split by ',') + size_t suite_name_count;// Number of suite names + int suite_name_has_wildcard; // 1 if any suite name contains '*', 0 otherwise + const char* tag; // Value for --tag + cstr *tag_list; // Array of tags (split by ',') + size_t tag_count; // Number of tags + int tag_has_wildcard; // 1 if any tag contains '*', 0 otherwise } filter; // Filter command flags struct { @@ -772,10 +811,6 @@ void pizza_io_flush(void); // string management // ***************************************************************************** -/* Type definitions */ -typedef char* cstr; -typedef const char* ccstr; - /** * @brief Creates a new cstr with the given initial value. * @@ -968,6 +1003,10 @@ cstr pizza_io_cstr_pad_left(ccstr str, size_t total_length, char pad_char); */ cstr pizza_io_cstr_pad_right(ccstr str, size_t total_length, char pad_char); +// Append a string to a buffer safely with NUL-termination. +// Returns true on success, false if buffer would overflow. +bool pizza_io_cstr_append(cstr dest, size_t max_len, cstr src); + #ifdef __cplusplus } #endif diff --git a/code/logic/fossil/pizza/mock.h b/code/logic/fossil/pizza/mock.h index 583c6687..43741dac 100644 --- a/code/logic/fossil/pizza/mock.h +++ b/code/logic/fossil/pizza/mock.h @@ -21,6 +21,8 @@ extern "C" { #endif +// TODO: Upgrade to have Truthful Intelligent Mocking (TIM) capabilities + // ***************************************************************************** // Type declarations // ***************************************************************************** diff --git a/code/logic/fossil/pizza/test.h b/code/logic/fossil/pizza/test.h index 4733974c..9eac5938 100644 --- a/code/logic/fossil/pizza/test.h +++ b/code/logic/fossil/pizza/test.h @@ -46,16 +46,34 @@ typedef struct { int empty; } fossil_pizza_score_t; +// --- Test Meta --- +typedef struct { + char* hash; // Hash of input/output/test logic + char* prev_hash; // Link to previous block (optional) + time_t timestamp; // When the test was run + char* origin_device_id; // Where it was run + char* author; // Who defined the test + double trust_score; // TI trust score (0.0 - 1.0) + double confidence; // Result confidence + bool immutable; // If true, cannot be changed + char* signature; // Digital signature of test result +} fossil_pizza_meta_t; + // --- Test Case --- typedef struct { - char* name; - char* tags; - char* criteria; - void (*setup)(void); - void (*teardown)(void); - void (*run)(void); - uint64_t elapsed_ns; - fossil_pizza_case_result_t result; + char* name; // Test name + char* tags; // Comma-separated tags + char* criteria; // Description or expectations + + void (*setup)(void); // Optional setup + void (*teardown)(void); // Optional teardown + void (*run)(void); // Test execution function + + uint64_t elapsed_ns; // Timing in nanoseconds + fossil_pizza_case_result_t result; // Outcome + + // TI Extensions: + fossil_pizza_meta_t meta; // Metadata for TI auditing } fossil_pizza_case_t; // In fossil_pizza_suite_t @@ -64,12 +82,18 @@ typedef struct { fossil_pizza_case_t* cases; size_t count; size_t capacity; + void (*setup)(void); void (*teardown)(void); + uint64_t time_elapsed_ns; int total_score; int total_possible; + fossil_pizza_score_t score; + + // TI Extensions: + fossil_pizza_meta_t meta; // Metadata for suite-level auditing } fossil_pizza_suite_t; // In fossil_pizza_engine_t @@ -77,10 +101,16 @@ typedef struct { fossil_pizza_suite_t* suites; size_t count; size_t capacity; + int score_total; int score_possible; + fossil_pizza_score_t score; - fossil_pizza_pallet_t pallet; + + fossil_pizza_pallet_t pallet; // CLI + config + + // TI Extensions: + fossil_pizza_meta_t meta; // Global engine metadata (hash of all runs) } fossil_pizza_engine_t; // --- Initialization --- @@ -149,6 +179,15 @@ int32_t fossil_pizza_end(fossil_pizza_engine_t* engine); */ void pizza_test_assert_internal(bool condition, const char *message, const char *file, int line, const char *func); +/** + * @brief Internal function to handle assertions with message formatting. + * + * This function is used internally by the test framework to handle assertions + * and format messages. It is not intended to be called directly. + * + * @param message The message to format. + * @return A formatted message string. + */ char *pizza_test_assert_messagef(const char *message, ...); // ********************************************************************************************* @@ -213,34 +252,56 @@ void _on_skip(const char *description); */ #ifdef __cplusplus - #define _FOSSIL_TEST(test_name) \ - extern "C" void test_name##_run(void); \ - static fossil_pizza_case_t test_case_##test_name = { \ - (cstr)#test_name, \ - (cstr)"fossil", \ - (cstr)"name", \ - nullptr, \ - nullptr, \ - test_name##_run, \ - 0, \ - FOSSIL_PIZZA_CASE_EMPTY \ - }; \ - extern "C" void test_name##_run(void) - #else - #define _FOSSIL_TEST(test_name) \ - void test_name##_run(void); \ - static fossil_pizza_case_t test_case_##test_name = { \ - .name = #test_name, \ - .tags = "fossil", \ - .criteria = "name", \ - .setup = NULL, \ - .teardown = NULL, \ - .run = test_name##_run, \ - .elapsed_ns = 0, \ - .result = FOSSIL_PIZZA_CASE_EMPTY \ - }; \ - void test_name##_run(void) - #endif +#define _FOSSIL_TEST(test_name) \ + extern "C" void test_name##_run(void); \ + static fossil_pizza_case_t test_case_##test_name = { \ + (char*)#test_name, \ + (char*)"fossil", \ + (char*)"name", \ + nullptr, \ + nullptr, \ + test_name##_run, \ + 0, \ + FOSSIL_PIZZA_CASE_EMPTY, \ + { \ + nullptr, \ + nullptr, \ + 0, \ + (char*)"unknown", \ + (char*)"anonymous", \ + 0.0, \ + 0.0, \ + false, \ + nullptr \ + } \ + }; \ + extern "C" void test_name##_run(void) +#else +#define _FOSSIL_TEST(test_name) \ + void test_name##_run(void); \ + static fossil_pizza_case_t test_case_##test_name = { \ + .name = #test_name, \ + .tags = "fossil", \ + .criteria = "name", \ + .setup = NULL, \ + .teardown = NULL, \ + .run = test_name##_run, \ + .elapsed_ns = 0, \ + .result = FOSSIL_PIZZA_CASE_EMPTY, \ + .meta = { \ + .hash = NULL, \ + .prev_hash = NULL, \ + .timestamp = 0, \ + .origin_device_id = "unknown", \ + .author = "anonymous", \ + .trust_score = 0.0, \ + .confidence = 0.0, \ + .immutable = false, \ + .signature = NULL \ + } \ + }; \ + void test_name##_run(void) +#endif /** @brief Macro to set a test case's tags. * @@ -316,7 +377,7 @@ void _on_skip(const char *description); void setup_##suite(void); \ void teardown_##suite(void); \ static fossil_pizza_suite_t suite_##suite { \ - (cstr)#suite, \ + (char*)#suite, \ nullptr, \ 0, \ 0, \ @@ -325,7 +386,18 @@ void _on_skip(const char *description); 0, \ 0, \ 0, \ - {0, 0, 0, 0, 0, 0} \ + {0, 0, 0, 0, 0, 0}, \ + { \ + nullptr, /* hash */ \ + nullptr, /* prev_hash */ \ + 0, /* timestamp */ \ + (char*)"unknown",/* origin_device_id */ \ + (char*)"anonymous", /* author */ \ + 0.0, /* trust_score */ \ + 0.0, /* confidence */ \ + false, /* immutable */ \ + nullptr /* signature */ \ + } \ } #else #define _FOSSIL_SUITE(suite) \ @@ -341,7 +413,18 @@ void _on_skip(const char *description); .time_elapsed_ns = 0, \ .total_score = 0, \ .total_possible = 0, \ - .score = {0, 0, 0, 0, 0, 0} \ + .score = {0, 0, 0, 0, 0, 0}, \ + .meta = { \ + .hash = NULL, \ + .prev_hash = NULL, \ + .timestamp = 0, \ + .origin_device_id = "unknown", \ + .author = "anonymous", \ + .trust_score = 0.0, \ + .confidence = 0.0, \ + .immutable = false, \ + .signature = NULL \ + } \ } #endif diff --git a/code/logic/test.c b/code/logic/test.c index 3848173a..a546a7a3 100644 --- a/code/logic/test.c +++ b/code/logic/test.c @@ -37,14 +37,28 @@ int fossil_pizza_start(fossil_pizza_engine_t* engine, int argc, char** argv) { if (!engine || !argv) return FOSSIL_PIZZA_FAILURE; pizza_sys_memory_set(engine, 0, sizeof(*engine)); - engine->suites = null; + + engine->suites = NULL; engine->count = 0; engine->capacity = 0; engine->score_total = 0; engine->score_possible = 0; - engine->pallet = fossil_pizza_pallet_create(argc, argv); + pizza_sys_memory_set(&engine->score, 0, sizeof(engine->score)); + engine->pallet = fossil_pizza_pallet_create(argc, argv); + + // --- TI Meta Initialization --- + engine->meta.hash = NULL; + engine->meta.prev_hash = NULL; + engine->meta.timestamp = time(NULL); + engine->meta.origin_device_id = "unknown"; + engine->meta.author = "anonymous"; + engine->meta.trust_score = 0.0; + engine->meta.confidence = 0.0; + engine->meta.immutable = false; + engine->meta.signature = NULL; + // Parse configuration file if specified const char* config_file = engine->pallet.config_file; if (config_file && fossil_pizza_ini_parse(config_file, &engine->pallet) != FOSSIL_PIZZA_SUCCESS) { @@ -58,6 +72,8 @@ int fossil_pizza_start(fossil_pizza_engine_t* engine, int argc, char** argv) { // --- Add Suite --- int fossil_pizza_add_suite(fossil_pizza_engine_t* engine, fossil_pizza_suite_t suite) { if (!engine) return FOSSIL_PIZZA_FAILURE; + + // Resize if needed if (engine->count >= engine->capacity) { size_t new_cap = engine->capacity ? engine->capacity * 2 : 4; fossil_pizza_suite_t* resized = pizza_sys_memory_realloc(engine->suites, new_cap * sizeof(*engine->suites)); @@ -65,13 +81,56 @@ int fossil_pizza_add_suite(fossil_pizza_engine_t* engine, fossil_pizza_suite_t s engine->suites = resized; engine->capacity = new_cap; } + + // --- TI Metadata Initialization --- + suite.meta.timestamp = time(NULL); + if (!suite.meta.origin_device_id) suite.meta.origin_device_id = "unknown"; + if (!suite.meta.author) suite.meta.author = "anonymous"; + + suite.meta.trust_score = 0.0; + suite.meta.confidence = 0.0; + suite.meta.immutable = false; + + // Previous hash from engine + char* prev_hash = engine->meta.hash ? engine->meta.hash : ""; + suite.meta.prev_hash = prev_hash; + + // Prepare input string + char input_buf[1000] = {0}; + if (!pizza_io_cstr_append(input_buf, sizeof(input_buf), suite.suite_name) || + !pizza_io_cstr_append(input_buf, sizeof(input_buf), suite.meta.author) || + !pizza_io_cstr_append(input_buf, sizeof(input_buf), suite.meta.origin_device_id)) { + return FOSSIL_PIZZA_FAILURE; + } + + // Compute hash + uint8_t hash_raw[32]; + fossil_pizza_hash(input_buf, prev_hash, hash_raw); + + // Convert to hex + char* hash_hex = pizza_sys_memory_alloc(65); + if (!hash_hex) return FOSSIL_PIZZA_FAILURE; + + static const char hex[] = "0123456789abcdef"; + for (int i = 0; i < 32; ++i) { + hash_hex[i * 2] = hex[(hash_raw[i] >> 4) & 0xF]; + hash_hex[i * 2 + 1] = hex[hash_raw[i] & 0xF]; + } + hash_hex[64] = '\0'; + suite.meta.hash = hash_hex; + + // Add to engine engine->suites[engine->count++] = suite; return FOSSIL_PIZZA_SUCCESS; } // --- Add Case --- +#define FOSSIL_PIZZA_HASH_HEX_LEN 65 + int fossil_pizza_add_case(fossil_pizza_suite_t* suite, fossil_pizza_case_t test_case) { if (!suite) return FOSSIL_PIZZA_FAILURE; + + // Resize case array if needed if (suite->count >= suite->capacity) { size_t new_cap = suite->capacity ? suite->capacity * 2 : 4; fossil_pizza_case_t* resized = pizza_sys_memory_realloc(suite->cases, new_cap * sizeof(*suite->cases)); @@ -79,12 +138,53 @@ int fossil_pizza_add_case(fossil_pizza_suite_t* suite, fossil_pizza_case_t test_ suite->cases = resized; suite->capacity = new_cap; } + + // --- TI Metadata Initialization --- + test_case.meta.timestamp = time(NULL); + test_case.meta.origin_device_id = test_case.meta.origin_device_id ? test_case.meta.origin_device_id : "unknown"; + test_case.meta.author = test_case.meta.author ? test_case.meta.author : "anonymous"; + test_case.meta.trust_score = 0.0; + test_case.meta.confidence = 0.0; + test_case.meta.immutable = false; + + // Link to suite’s hash as previous + test_case.meta.prev_hash = suite->meta.hash ? suite->meta.hash : NULL; + + // Prepare input string + char input_buf[1000] = {0}; + if (!pizza_io_cstr_append(input_buf, sizeof(input_buf), test_case.name) || + !pizza_io_cstr_append(input_buf, sizeof(input_buf), test_case.criteria) || + !pizza_io_cstr_append(input_buf, sizeof(input_buf), test_case.meta.author)) { + return FOSSIL_PIZZA_FAILURE; // Overflow occurred + } + + // Compute hash + uint8_t hash_raw[32]; + char* prev_hash = test_case.meta.prev_hash ? test_case.meta.prev_hash : ""; + fossil_pizza_hash(input_buf, prev_hash, hash_raw); + + // Convert hash to hex string + char* hash_hex = pizza_sys_memory_alloc(FOSSIL_PIZZA_HASH_HEX_LEN); + if (!hash_hex) return FOSSIL_PIZZA_FAILURE; + + for (int i = 0; i < 32; ++i) + sprintf(&hash_hex[i * 2], "%02x", hash_raw[i]); + + test_case.meta.hash = hash_hex; + + // Add test case to suite suite->cases[suite->count++] = test_case; return FOSSIL_PIZZA_SUCCESS; } // --- Update Score --- void fossil_pizza_update_score(fossil_pizza_case_t* test_case, fossil_pizza_suite_t* suite) { + if (!test_case || !suite) return; + + // --- TI: Set result timestamp --- + test_case->meta.timestamp = time(NULL); + + // --- TI: Compute score and validity --- switch (test_case->result) { case FOSSIL_PIZZA_CASE_PASS: suite->score.passed++; @@ -107,7 +207,65 @@ void fossil_pizza_update_score(fossil_pizza_case_t* test_case, fossil_pizza_suit suite->score.empty++; break; } + suite->total_possible++; + + // --- TI: Build hash input --- + char input_buf[512] = {0}; + char temp[64]; + + if (pizza_io_cstr_append(input_buf, sizeof(input_buf), test_case->name ? test_case->name : "unnamed") != 0) + return; + + if (pizza_io_cstr_append(input_buf, sizeof(input_buf), test_case->meta.author ? test_case->meta.author : "anonymous") != 0) + return; + + if (pizza_io_cstr_append(input_buf, sizeof(input_buf), test_case->meta.origin_device_id ? test_case->meta.origin_device_id : "unknown") != 0) + return; + + snprintf(temp, sizeof(temp), "%d", test_case->result); + if (pizza_io_cstr_append(input_buf, sizeof(input_buf), temp) != 0) return; + + snprintf(temp, sizeof(temp), "%.2f", test_case->meta.trust_score); + if (pizza_io_cstr_append(input_buf, sizeof(input_buf), temp) != 0) return; + + snprintf(temp, sizeof(temp), "%.2f", test_case->meta.confidence); + if (pizza_io_cstr_append(input_buf, sizeof(input_buf), temp) != 0) return; + + snprintf(temp, sizeof(temp), "%lld", (long long)test_case->meta.timestamp); + if (pizza_io_cstr_append(input_buf, sizeof(input_buf), temp) != 0) return; + + // --- TI: Determine previous hash --- + char* prev_hash = NULL; + if (suite->count > 0) { + fossil_pizza_case_t* last_case = &suite->cases[suite->count - 1]; + if (last_case != test_case && last_case->meta.hash) { + prev_hash = last_case->meta.hash; + } + } + test_case->meta.prev_hash = prev_hash; + + // --- TI: Hash it --- + uint8_t hash_raw[32]; + fossil_pizza_hash(input_buf, prev_hash ? prev_hash : "", hash_raw); + + char* hash_hex = pizza_sys_memory_alloc(65); + if (!hash_hex) return; + + for (int i = 0; i < 32; ++i) + snprintf(&hash_hex[i * 2], 3, "%02x", hash_raw[i]); + + test_case->meta.hash = hash_hex; +} + +void fossil_pizza_cleanup_suite(fossil_pizza_suite_t* suite) { + if (!suite) return; + for (size_t i = 0; i < suite->count; ++i) { + if (suite->cases[i].meta.hash) { + pizza_sys_memory_free(suite->cases[i].meta.hash); + suite->cases[i].meta.hash = NULL; + } + } } // --- Show Test Cases --- @@ -473,54 +631,65 @@ static uint64_t seconds_to_nanoseconds(uint64_t seconds) { } void fossil_pizza_run_test(const fossil_pizza_engine_t* engine, fossil_pizza_case_t* test_case, fossil_pizza_suite_t* suite) { - if (!test_case || !suite) return; + if (!test_case || !suite || !engine) return; - // Check if the test case name matches the --only filter + // --- Filter: --only --- if (engine->pallet.run.only && pizza_io_cstr_compare(engine->pallet.run.only, test_case->name) != 0) { - return; // Skip this test case if it doesn't match the --only filter + return; } - // Check if the test case name matches the --skip filter + // --- Filter: --skip --- if (engine->pallet.run.skip && pizza_io_cstr_compare(engine->pallet.run.skip, test_case->name) == 0) { test_case->result = FOSSIL_PIZZA_CASE_SKIPPED; - return; // Skip this test case if it matches the --skip filter + fossil_pizza_update_score(test_case, suite); + return; } - for (int i = 0; i < (engine->pallet.run.repeat ? engine->pallet.run.repeat : 1); ++i) { + size_t repeat_count = (size_t)(engine->pallet.run.repeat > 0 ? engine->pallet.run.repeat : 1); + + for (size_t i = 0; i < repeat_count; ++i) { if (test_case->setup) test_case->setup(); + test_case->result = FOSSIL_PIZZA_CASE_EMPTY; uint64_t start_time = fossil_pizza_now_ns(); if (test_case->run) { if (setjmp(test_jump_buffer) == 0) { test_case->run(); - uint64_t elapsed_time = fossil_pizza_now_ns() - start_time; + uint64_t end_time = fossil_pizza_now_ns(); + uint64_t elapsed = end_time - start_time; + test_case->elapsed_ns = elapsed; + + #ifndef FOSSIL_PIZZA_TIMEOUT + #define FOSSIL_PIZZA_TIMEOUT 60 + #endif - if (elapsed_time > seconds_to_nanoseconds(G_PIZZA_TIMEOUT)) { // 1 minute in nanoseconds + if (elapsed > seconds_to_nanoseconds(FOSSIL_PIZZA_TIMEOUT)) { test_case->result = FOSSIL_PIZZA_CASE_TIMEOUT; } else { test_case->result = FOSSIL_PIZZA_CASE_PASS; } } else { test_case->result = FOSSIL_PIZZA_CASE_FAIL; + test_case->elapsed_ns = fossil_pizza_now_ns() - start_time; + if (engine->pallet.run.fail_fast) { - return; // Exit immediately if --fail-fast is enabled + fossil_pizza_update_score(test_case, suite); + fossil_pizza_show_cases(suite, engine); + return; } } } else { test_case->result = FOSSIL_PIZZA_CASE_EMPTY; + test_case->elapsed_ns = 0; } - test_case->elapsed_ns = fossil_pizza_now_ns() - start_time; if (test_case->teardown) test_case->teardown(); } - // Output test case result - fossil_pizza_show_cases(suite, engine); - - // Update scores based on result fossil_pizza_update_score(test_case, suite); - _ASSERT_COUNT = 0; // Reset the assertion count for the next test case + fossil_pizza_show_cases(suite, engine); + _ASSERT_COUNT = 0; } // --- Algorithmic modifications --- @@ -635,17 +804,20 @@ void fossil_pizza_shuffle_cases(fossil_pizza_suite_t* suite, const fossil_pizza_ // --- Run One Suite --- int fossil_pizza_run_suite(const fossil_pizza_engine_t* engine, fossil_pizza_suite_t* suite) { - if (!suite) return FOSSIL_PIZZA_FAILURE; + if (!suite || !suite->cases) return FOSSIL_PIZZA_FAILURE; + if (suite->setup) suite->setup(); + // --- TI meta timestamp --- + suite->meta.timestamp = time(NULL); + + // --- Reset suite stats --- suite->time_elapsed_ns = fossil_pizza_now_ns(); suite->total_score = 0; suite->total_possible = 0; pizza_sys_memory_set(&suite->score, 0, sizeof(suite->score)); - if (!suite->cases) return FOSSIL_PIZZA_FAILURE; - - // Apply filtering, sorting, and shuffling + // --- Filtering --- fossil_pizza_case_t* filtered_cases[suite->count]; size_t filtered_count = fossil_pizza_filter_cases(suite, engine, filtered_cases); @@ -662,24 +834,62 @@ int fossil_pizza_run_suite(const fossil_pizza_engine_t* engine, fossil_pizza_sui suite->time_elapsed_ns = fossil_pizza_now_ns() - suite->time_elapsed_ns; if (suite->teardown) suite->teardown(); + + // --- TI: Generate suite hash --- + char input_buf[512] = {0}; + + // Identity components + pizza_io_cstr_append(input_buf, sizeof(input_buf), suite->suite_name); + pizza_io_cstr_append(input_buf, sizeof(input_buf), suite->meta.author ? suite->meta.author : "anonymous"); + pizza_io_cstr_append(input_buf, sizeof(input_buf), suite->meta.origin_device_id ? suite->meta.origin_device_id : "unknown"); + + // Execution time + char temp[64]; + snprintf(temp, sizeof(temp), "%" PRIu64, suite->time_elapsed_ns); + pizza_io_cstr_append(input_buf, sizeof(input_buf), temp); + + // Pass/fail stats + snprintf(temp, sizeof(temp), "%d", suite->score.passed); + pizza_io_cstr_append(input_buf, sizeof(input_buf), temp); + snprintf(temp, sizeof(temp), "%d", suite->score.failed); + pizza_io_cstr_append(input_buf, sizeof(input_buf), temp); + + // Previous hash (for chaining) + char* prev_hash = (engine && engine->meta.hash) ? engine->meta.hash : NULL; + + static uint8_t hash_raw[32]; + fossil_pizza_hash(input_buf, prev_hash ? prev_hash : "", hash_raw); + + static char hash_buf[65]; + for (int i = 0; i < 32; ++i) + snprintf(&hash_buf[i * 2], 3, "%02x", hash_raw[i]); + + suite->meta.hash = hash_buf; + suite->meta.prev_hash = prev_hash; + return FOSSIL_PIZZA_SUCCESS; } // --- Run All Suites --- int fossil_pizza_run_all(fossil_pizza_engine_t* engine) { if (!engine) return FOSSIL_PIZZA_FAILURE; + + // --- Reset global engine score --- pizza_sys_memory_set(&engine->score, 0, sizeof(engine->score)); engine->score_total = 0; engine->score_possible = 0; + // --- TI: Record engine-level timestamp --- + engine->meta.timestamp = time(NULL); + + // --- Run all test suites --- for (size_t i = 0; i < engine->count; ++i) { fossil_pizza_run_suite(engine, &engine->suites[i]); - engine->score_total += engine->suites[i].total_score; + engine->score_total += engine->suites[i].total_score; engine->score_possible += engine->suites[i].total_possible; - // aggregate suite scores - fossil_pizza_score_t* src = &engine->suites[i].score; + const fossil_pizza_score_t* src = &engine->suites[i].score; engine->score.passed += src->passed; engine->score.failed += src->failed; engine->score.skipped += src->skipped; @@ -687,10 +897,74 @@ int fossil_pizza_run_all(fossil_pizza_engine_t* engine) { engine->score.unexpected += src->unexpected; engine->score.empty += src->empty; } + + // --- TI: Generate engine hash --- + char input_buf[512] = {0}; + pizza_io_cstr_append(input_buf, sizeof(input_buf), engine->meta.author ? engine->meta.author : "anonymous"); + pizza_io_cstr_append(input_buf, sizeof(input_buf), engine->meta.origin_device_id ? engine->meta.origin_device_id : "unknown"); + + char temp[64]; + snprintf(temp, sizeof(temp), "%d", engine->score_total); + pizza_io_cstr_append(input_buf, sizeof(input_buf), temp); + + snprintf(temp, sizeof(temp), "%d", engine->score_possible); + pizza_io_cstr_append(input_buf, sizeof(input_buf), temp); + + snprintf(temp, sizeof(temp), "%d", engine->score.passed); + pizza_io_cstr_append(input_buf, sizeof(input_buf), temp); + + snprintf(temp, sizeof(temp), "%d", engine->score.failed); + pizza_io_cstr_append(input_buf, sizeof(input_buf), temp); + + // Chain from last suite hash (if any) + char* prev_hash = NULL; + if (engine->count > 0 && engine->suites[engine->count - 1].meta.hash) { + prev_hash = engine->suites[engine->count - 1].meta.hash; + } + + static uint8_t hash_raw[32]; + fossil_pizza_hash(input_buf, prev_hash ? prev_hash : "", hash_raw); + + static char hash_buf[65]; + for (int i = 0; i < 32; ++i) + snprintf(&hash_buf[i * 2], 3, "%02x", hash_raw[i]); + + engine->meta.hash = hash_buf; + engine->meta.prev_hash = prev_hash; + return FOSSIL_PIZZA_SUCCESS; } // --- Summary Report --- +const char* fossil_test_summary_feedback(const fossil_pizza_score_t* score) { + if (!score) return "No results available."; + + static char message[256]; + int total = score->passed + score->failed + score->skipped + + score->timeout + score->unexpected + score->empty; + + if (total == 0) return "No tests were run."; + + double pass_rate = (double)score->passed / total * 100.0; + double fail_ratio = (double)(score->failed + score->unexpected) / total; + + if (pass_rate == 100.0) { + snprintf(message, sizeof(message), "Perfect run! All tests passed. Great job."); + } else if (fail_ratio > 0.5) { + snprintf(message, sizeof(message), "High failure rate detected. Investigate the failing and unexpected tests."); + } else if (score->timeout > 0) { + snprintf(message, sizeof(message), "Some tests timed out. Check for infinite loops or delays."); + } else if (score->skipped > 0) { + snprintf(message, sizeof(message), "Some tests were skipped. Make sure all dependencies are in place."); + } else if (score->empty > 0 && score->passed == 0) { + snprintf(message, sizeof(message), "All tests are empty or unimplemented."); + } else { + snprintf(message, sizeof(message), "Test run completed. Review failures and improve reliability."); + } + + return message; +} + void fossil_pizza_summary_timestamp(const fossil_pizza_engine_t* engine) { if (!engine) return; @@ -699,95 +973,105 @@ void fossil_pizza_summary_timestamp(const fossil_pizza_engine_t* engine) { total_elapsed_ns += engine->suites[i].time_elapsed_ns; } + // Convert total nanoseconds into detailed breakdown uint64_t total_elapsed_us = total_elapsed_ns / 1000; uint64_t total_elapsed_ms = total_elapsed_us / 1000; uint64_t total_elapsed_s = total_elapsed_ms / 1000; - uint64_t minutes = total_elapsed_s / 60; - uint64_t seconds = total_elapsed_s % 60; - uint64_t microseconds = total_elapsed_us % 1000; - uint64_t nanoseconds = total_elapsed_ns % 1000; + uint64_t hours = total_elapsed_s / 3600; + uint64_t minutes = (total_elapsed_s % 3600) / 60; + uint64_t seconds = total_elapsed_s % 60; + uint64_t microseconds = total_elapsed_us % 1000; + uint64_t nanoseconds = total_elapsed_ns % 1000; + + // Format: HH:MM:SS.UUU,NNN + char time_buffer[64]; + snprintf(time_buffer, sizeof(time_buffer), + "%02llu:%02llu:%02llu.%03llu,%03llu", + (unsigned long long)hours, + (unsigned long long)minutes, + (unsigned long long)seconds, + (unsigned long long)microseconds, + (unsigned long long)nanoseconds); + + // Display based on theme switch (engine->pallet.theme) { case PIZZA_THEME_FOSSIL: pizza_io_printf("{blue,bold}\n========================================================================={reset}\n"); - pizza_io_printf("{blue,bold}Elapsed Time:{white} %llu minutes, %llu seconds, %llu microseconds, %llu nanoseconds\n{reset}", - minutes, seconds, microseconds, nanoseconds); + pizza_io_printf("{blue,bold}Elapsed Time:{white} %s (hh:mm:ss.micro,nano)\n{reset}", time_buffer); pizza_io_printf("{blue,bold}========================================================================={reset}\n"); break; case PIZZA_THEME_CATCH: case PIZZA_THEME_DOCTEST: pizza_io_printf("{magenta}\n========================================================================={reset}\n"); - pizza_io_printf("{magenta}Elapsed Time:{reset} %llu minutes, %llu seconds, %llu microseconds, %llu nanoseconds\n", - minutes, seconds, microseconds, nanoseconds); + pizza_io_printf("{magenta}Elapsed Time:{reset} %s (hh:mm:ss.micro,nano)\n", time_buffer); pizza_io_printf("{magenta}========================================================================={reset}\n"); break; case PIZZA_THEME_CPPUTEST: pizza_io_printf("{cyan}\n========================================================================={reset}\n"); - pizza_io_printf("{cyan}[Elapsed Time]:{reset} %llu minutes, %llu seconds, %llu microseconds, %llu nanoseconds\n", - minutes, seconds, microseconds, nanoseconds); + pizza_io_printf("{cyan}[Elapsed Time]:{reset} %s (hh:mm:ss.micro,nano)\n", time_buffer); pizza_io_printf("{cyan}========================================================================={reset}\n"); break; case PIZZA_THEME_TAP: - pizza_io_printf("\n# {yellow}Elapsed Time:{reset} %llu minutes, %llu seconds, %llu microseconds, %llu nanoseconds\n", - minutes, seconds, microseconds, nanoseconds); + pizza_io_printf("\n# {yellow}Elapsed Time:{reset} %s (hh:mm:ss.micro,nano)\n", time_buffer); break; case PIZZA_THEME_GOOGLETEST: - pizza_io_printf("[==========] {blue}Elapsed Time:{reset} %llu minutes, %llu seconds, %llu microseconds, %llu nanoseconds\n", - minutes, seconds, microseconds, nanoseconds); + pizza_io_printf("[==========] {blue}Elapsed Time:{reset} %s (hh:mm:ss.micro,nano)\n", time_buffer); break; case PIZZA_THEME_UNITY: - pizza_io_printf("{green}Unity Test Elapsed Time{reset}\n"); - pizza_io_printf("{cyan}Minutes:{reset} %llu, {cyan}Seconds:{reset} %llu, {cyan}Microseconds:{reset} %llu, {cyan}Nanoseconds:{reset} %llu\n", - minutes, seconds, microseconds, nanoseconds); + pizza_io_printf("{green}Unity Test Elapsed Time:{reset}\n"); + pizza_io_printf("{cyan}HH:MM:SS:{reset} %02llu:%02llu:%02llu, {cyan}Micro:{reset} %03llu, {cyan}Nano:{reset} %03llu\n", + hours, minutes, seconds, microseconds, nanoseconds); break; default: pizza_io_printf("Unknown theme. Unable to display elapsed time.\n"); - break; + break; } // Calculate averages double avg_time_per_suite_ns = (engine->count > 0) ? (double)total_elapsed_ns / engine->count : 0; double avg_time_per_test_ns = (engine->score_possible > 0) ? (double)total_elapsed_ns / engine->score_possible : 0; + double avg_suite_us = avg_time_per_suite_ns / 1000.0; + double avg_suite_ms = avg_time_per_suite_ns / 1e6; + double avg_test_us = avg_time_per_test_ns / 1000.0; + double avg_test_ms = avg_time_per_test_ns / 1e6; + switch (engine->pallet.theme) { case PIZZA_THEME_FOSSIL: - pizza_io_printf("{blue,bold}Average Time per Suite:{white} %.2f nanoseconds\n{reset}", avg_time_per_suite_ns); - pizza_io_printf("{blue,bold}Average Time per Test :{white} %.2f nanoseconds\n{reset}", avg_time_per_test_ns); + pizza_io_printf("{blue,bold}Average Time per Suite:{white} %.2f ns (%.2f µs | %.3f ms)\n{reset}", avg_time_per_suite_ns, avg_suite_us, avg_suite_ms); + pizza_io_printf("{blue,bold}Average Time per Test :{white} %.2f ns (%.2f µs | %.3f ms)\n{reset}", avg_time_per_test_ns, avg_test_us, avg_test_ms); pizza_io_printf("{blue,bold}========================================================================={reset}\n"); break; case PIZZA_THEME_CATCH: case PIZZA_THEME_DOCTEST: - pizza_io_printf("Average Time per Suite: %.2f nanoseconds\n", avg_time_per_suite_ns); - pizza_io_printf("Average Time per Test : %.2f nanoseconds\n", avg_time_per_test_ns); + case PIZZA_THEME_UNITY: + pizza_io_printf("Average Time per Suite: %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_suite_ns, avg_suite_us, avg_suite_ms); + pizza_io_printf("Average Time per Test : %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_test_ns, avg_test_us, avg_test_ms); pizza_io_printf("=========================================================================\n"); break; case PIZZA_THEME_CPPUTEST: - pizza_io_printf("[Average Time per Suite]: %.2f nanoseconds\n", avg_time_per_suite_ns); - pizza_io_printf("[Average Time per Test ]: %.2f nanoseconds\n", avg_time_per_test_ns); + pizza_io_printf("[Average Time per Suite]: %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_suite_ns, avg_suite_us, avg_suite_ms); + pizza_io_printf("[Average Time per Test ]: %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_test_ns, avg_test_us, avg_test_ms); pizza_io_printf("=========================================================================\n"); break; case PIZZA_THEME_TAP: - pizza_io_printf("# Average Time per Suite: %.2f nanoseconds\n", avg_time_per_suite_ns); - pizza_io_printf("# Average Time per Test : %.2f nanoseconds\n", avg_time_per_test_ns); + pizza_io_printf("# Average Time per Suite: %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_suite_ns, avg_suite_us, avg_suite_ms); + pizza_io_printf("# Average Time per Test : %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_test_ns, avg_test_us, avg_test_ms); break; case PIZZA_THEME_GOOGLETEST: - pizza_io_printf("[----------] Average Time per Suite: %.2f nanoseconds\n", avg_time_per_suite_ns); - pizza_io_printf("[----------] Average Time per Test : %.2f nanoseconds\n", avg_time_per_test_ns); - break; - - case PIZZA_THEME_UNITY: - pizza_io_printf("Average Time per Suite: %.2f nanoseconds\n", avg_time_per_suite_ns); - pizza_io_printf("Average Time per Test : %.2f nanoseconds\n", avg_time_per_test_ns); + pizza_io_printf("[----------] Average Time per Suite: %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_suite_ns, avg_suite_us, avg_suite_ms); + pizza_io_printf("[----------] Average Time per Test : %.2f ns (%.2f µs | %.3f ms)\n", avg_time_per_test_ns, avg_test_us, avg_test_ms); break; default: @@ -953,6 +1237,8 @@ void fossil_pizza_summary(const fossil_pizza_engine_t* engine) { fossil_pizza_summary_heading(engine); fossil_pizza_summary_scoreboard(engine); fossil_pizza_summary_timestamp(engine); + const char* msg = fossil_test_summary_feedback(&engine->score); + pizza_io_printf("\n{bold}{blue}Feedback:{reset} %s\n", msg); } // --- End / Cleanup --- @@ -976,15 +1262,35 @@ int32_t fossil_pizza_end(fossil_pizza_engine_t* engine) { // -- Assume -- +typedef struct { + char *message; + uint8_t hash[FOSSIL_PIZZA_HASH_SIZE]; + uint64_t timestamp; +} pizza_assert_ti_result; + +extern uint64_t get_pizza_time_microseconds(void); // from common utilities + char *pizza_test_assert_messagef(const char *message, ...) { va_list args; va_start(args, message); - size_t buffer_size = 1024; // Define a reasonable buffer size + + size_t buffer_size = 1024; char *formatted_message = (char *)pizza_sys_memory_alloc(buffer_size); + + pizza_assert_ti_result result = {0}; + if (formatted_message) { pizza_io_vsnprintf(formatted_message, buffer_size, message, args); formatted_message[buffer_size - 1] = '\0'; // Ensure null-termination + + // TI upgrade: compute hash and timestamp + result.message = formatted_message; + result.timestamp = get_pizza_time_microseconds(); + + // Hash both format string and final message to detect templating vs content errors + fossil_pizza_hash(message, formatted_message, result.hash); } + va_end(args); return formatted_message; } @@ -1045,43 +1351,43 @@ void pizza_test_assert_internal_output(const char *message, const char *file, in } } -static int pizza_test_assert_internal_detect(const char *message, const char *file, int line, const char *func) { - static const char *last_message = null; // Store the last assertion message - static const char *last_file = null; // Store the last file name - static int last_line = 0; // Store the last line number - static const char *last_func = null; // Store the last function name - static int anomaly_count = 0; // Counter for anomaly detection - - // Check if the current assertion is the same or similar to the last one - if (last_message && strstr(message, last_message) != null && - last_file && pizza_io_cstr_compare(last_file, file) == 0 && - last_line == line && - last_func && pizza_io_cstr_compare(last_func, func) == 0) { +static int pizza_test_assert_internal_detect_ti(const char *message, const char *file, int line, const char *func) { + static uint8_t last_hash[FOSSIL_PIZZA_HASH_SIZE] = {0}; + static int anomaly_count = 0; + + char input_buf[512], output_buf[64]; + snprintf(input_buf, sizeof(input_buf), "%s:%d:%s", file, line, func); + snprintf(output_buf, sizeof(output_buf), "%s", message); + + uint8_t current_hash[FOSSIL_PIZZA_HASH_SIZE]; + fossil_pizza_hash(input_buf, output_buf, current_hash); + + bool same_hash = memcmp(last_hash, current_hash, FOSSIL_PIZZA_HASH_SIZE) == 0; + + if (same_hash) { anomaly_count++; } else { - anomaly_count = 0; // Reset anomaly count for new assertion - last_message = message; - last_file = file; - last_line = line; - last_func = func; + anomaly_count = 0; + memcpy(last_hash, current_hash, FOSSIL_PIZZA_HASH_SIZE); } return anomaly_count; } void pizza_test_assert_internal(bool condition, const char *message, const char *file, int line, const char *func) { - _ASSERT_COUNT++; // Increment the assertion count + _ASSERT_COUNT++; if (!condition) { - int anomaly_count = pizza_test_assert_internal_detect(message, file, line, func); + int anomaly_count = pizza_test_assert_internal_detect_ti(message, file, line, func); - // Output assertion failure + // Enhanced output can include anomaly count and possibly hashed context pizza_test_assert_internal_output(message, file, line, func, anomaly_count); - longjmp(test_jump_buffer, 1); // Jump back to test case failure handler + longjmp(test_jump_buffer, 1); } } + // ********************************************************************************************* // internal messages // ********************************************************************************************* diff --git a/code/tests/cases/test_mark.cpp b/code/tests/cases/test_mark.cpp index 30ec6575..7500452e 100644 --- a/code/tests/cases/test_mark.cpp +++ b/code/tests/cases/test_mark.cpp @@ -85,7 +85,11 @@ MARK_BENCHMARK(elapsed_test); MARK_START(elapsed_test); // Simulate some work - for (int i = 0; i < 1000000; ++i); + int dummy = 0; + for (int i = 0; i < 1000000; ++i) { + dummy += i; + } + unused(dummy); // Prevent unused variable warning MARK_STOP(elapsed_test); ASSUME_ITS_TRUE(benchmark_elapsed_test.total_duration > 0.0); } diff --git a/meson.build b/meson.build index cd2386a2..182bcfce 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('Pizza Test', 'c', 'cpp', meson_version: '>=1.3.0', license: 'MPL-2.0', - version: '1.2.5', + version: '1.2.6', default_options: ['c_std=c11,c18', 'cpp_std=c++20']) subdir('code')