| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "libnetdata/libnetdata.h" |
| 4 | #include "stacktrace.h" |
| 5 | |
| 6 | // Structure to hold all test data |
| 7 | typedef struct { |
| 8 | BUFFER *direct_trace; // Buffer for direct stack trace |
| 9 | BUFFER *indirect_trace; // Buffer for indirect stack trace |
| 10 | BUFFER *direct_root_cause; // Root cause function from direct capture |
| 11 | BUFFER *indirect_root_cause; // Root cause function from indirect capture |
| 12 | const char *never_inline_fn; // Name of the never-inline function |
| 13 | const char *always_inline_fn; // Name of the always-inline function |
| 14 | } stacktrace_test_data_t; |
| 15 | |
| 16 | // Function to analyze a stack trace |
| 17 | static bool analyze_stack_trace( |
| 18 | const char *stack_trace, |
| 19 | const char *never_inline_fn, |
| 20 | const char *always_inline_fn, |
| 21 | const char *unittest_fn, |
| 22 | const char *root_cause) |
| 23 | { |
| 24 | fprintf(stderr, "--------------------------------------------------------------------------------\n"); |
| 25 | fprintf(stderr, "%s\n", stack_trace); |
| 26 | fprintf(stderr, "--------------------------------------------------------------------------------\n"); |
| 27 | |
| 28 | if (!stack_trace || !*stack_trace) { |
| 29 | fprintf(stderr, " - empty stack trace\n"); |
| 30 | return false; |
| 31 | } |
| 32 | |
| 33 | // Report presence of each function |
| 34 | bool never_inline_found = strstr(stack_trace, never_inline_fn) != NULL; |
| 35 | bool always_inline_found = strstr(stack_trace, always_inline_fn) != NULL; |
| 36 | bool unittest_found = strstr(stack_trace, unittest_fn) != NULL; |
| 37 | |
| 38 | fprintf(stderr, " - %50.50s: %s\n", |
| 39 | never_inline_fn, never_inline_found ? "FOUND" : "NOT FOUND"); |
| 40 | fprintf(stderr, " - %50.50s: %s\n", |
| 41 | always_inline_fn, always_inline_found ? "FOUND" : "NOT FOUND"); |
| 42 | fprintf(stderr, " - %50.50s: %s\n", |
| 43 | unittest_fn, unittest_found ? "FOUND" : "NOT FOUND"); |
| 44 | fprintf(stderr, " - %50.50s: %s\n", |
| 45 | "root cause function", |
| 46 | root_cause && *root_cause ? root_cause : "NOT FOUND"); |
| 47 | |
| 48 | // We only require the unittest function to be present for the test to pass |
| 49 | return unittest_found; |
| 50 | } |
| 51 | |
| 52 | // This function will never be inlined |
| 53 | NEVER_INLINE |
| 54 | static void never_inline_function_to_capture_stack_trace(stacktrace_test_data_t *test_data) { |
| 55 | test_data->never_inline_fn = __FUNCTION__; |
| 56 | |
| 57 | BUFFER *wb = buffer_create(4096, NULL); |
| 58 | |
| 59 | // Test 1: Direct capture |
| 60 | stacktrace_capture(wb); |
| 61 | buffer_strcat(test_data->direct_trace, buffer_tostring(wb)); |
| 62 | |
| 63 | // Get root cause (first time) |
| 64 | buffer_flush(test_data->direct_root_cause); |
| 65 | buffer_strcat(test_data->direct_root_cause, stacktrace_root_cause_function()); |
| 66 | |
| 67 | // Test 2: Indirect capture (get + to_buffer) |
| 68 | buffer_flush(wb); |
| 69 | STACKTRACE trace = stacktrace_get(0); |
| 70 | if (trace) { |
| 71 | stacktrace_to_buffer(trace, wb); |
| 72 | buffer_strcat(test_data->indirect_trace, buffer_tostring(wb)); |
| 73 | |
| 74 | // Get root cause (second time) |
| 75 | buffer_flush(test_data->indirect_root_cause); |
| 76 | buffer_strcat(test_data->indirect_root_cause, stacktrace_root_cause_function()); |
| 77 | } |
| 78 | |
| 79 | buffer_free(wb); |
| 80 | } |
| 81 | |
| 82 | // This function will be inlined in the caller |
| 83 | ALWAYS_INLINE |
| 84 | static void inline_function_to_capture_stack_trace(stacktrace_test_data_t *test_data) { |
| 85 | test_data->always_inline_fn = __FUNCTION__; |
| 86 | |
| 87 | // Call the non-inlined function |
| 88 | never_inline_function_to_capture_stack_trace(test_data); |
| 89 | } |
| 90 | |
| 91 | // Run the stacktrace unittest |
| 92 | int stacktrace_unittest(void) { |
| 93 | // Initialize stacktrace subsystem |
| 94 | stacktrace_init(); |
| 95 | |
| 96 | // Setup test data structure |
| 97 | stacktrace_test_data_t test_data = { |
| 98 | .direct_trace = buffer_create(4096, NULL), |
| 99 | .indirect_trace = buffer_create(4096, NULL), |
| 100 | .direct_root_cause = buffer_create(4096, NULL), |
| 101 | .indirect_root_cause = buffer_create(4096, NULL), |
| 102 | .never_inline_fn = NULL, |
| 103 | .always_inline_fn = NULL, |
| 104 | }; |
| 105 | |
| 106 | // Run the test function to gather stack traces |
| 107 | inline_function_to_capture_stack_trace(&test_data); |
| 108 | |
| 109 | // Print basic test information |
| 110 | fprintf(stderr, "\nSTACKTRACE TEST: Backend: %s\n", stacktrace_backend()); |
| 111 | |
| 112 | // Analyze both stack traces |
| 113 | fprintf(stderr, "\nDIRECT STACK TRACE\n"); |
| 114 | bool direct_analysis = analyze_stack_trace( |
| 115 | buffer_tostring(test_data.direct_trace), |
| 116 | test_data.never_inline_fn, |
| 117 | test_data.always_inline_fn, |
| 118 | "stacktrace_unittest", |
| 119 | buffer_tostring(test_data.direct_root_cause)); |
| 120 | |
| 121 | fprintf(stderr, "\nINDIRECT STACK TRACE\n"); |
| 122 | bool indirect_analysis = analyze_stack_trace( |
| 123 | buffer_tostring(test_data.indirect_trace), |
| 124 | test_data.never_inline_fn, |
| 125 | test_data.always_inline_fn, |
| 126 | "stacktrace_unittest", |
| 127 | buffer_tostring(test_data.indirect_root_cause)); |
| 128 | |
| 129 | // Free resources |
| 130 | buffer_free(test_data.direct_trace); |
| 131 | buffer_free(test_data.indirect_trace); |
| 132 | buffer_free(test_data.direct_root_cause); |
| 133 | buffer_free(test_data.indirect_root_cause); |
| 134 | |
| 135 | // Report overall test status - success if both analyses succeed |
| 136 | bool test_success = direct_analysis && indirect_analysis; |
| 137 | fprintf(stderr, "\nSTACKTRACE TEST: Overall result: %s\n", |
| 138 | test_success ? "SUCCESS" : "FAILURE"); |
| 139 | |
| 140 | return test_success ? 0 : 1; |
| 141 | } |