345 lines
11 KiB
C
345 lines
11 KiB
C
/*
|
|
* test_traces.c — Tests for OTLP trace processing helpers.
|
|
*
|
|
* Ported from internal/traces/traces_test.go (5 pure helper tests).
|
|
* The TestIngestOTLPJSON integration test is deferred (needs full MCP pipeline).
|
|
*/
|
|
#include "test_framework.h"
|
|
#include <traces/traces.h>
|
|
#include <string.h>
|
|
|
|
/* ── TestExtractServiceName ─────────────────────────────────────── */
|
|
|
|
TEST(traces_extract_service_name) {
|
|
cbm_trace_attr_t attrs[] = {
|
|
{.key = "service.name", .string_value = "order-service"},
|
|
};
|
|
cbm_trace_resource_t r = {.attributes = attrs, .attr_count = 1};
|
|
|
|
const char *name = cbm_extract_service_name(&r);
|
|
ASSERT_STR_EQ(name, "order-service");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_service_name_missing) {
|
|
cbm_trace_attr_t attrs[] = {
|
|
{.key = "other.attr", .string_value = "value"},
|
|
};
|
|
cbm_trace_resource_t r = {.attributes = attrs, .attr_count = 1};
|
|
|
|
const char *name = cbm_extract_service_name(&r);
|
|
ASSERT_STR_EQ(name, "");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_service_name_null) {
|
|
const char *name = cbm_extract_service_name(NULL);
|
|
ASSERT_STR_EQ(name, "");
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestExtractHTTPInfo ────────────────────────────────────────── */
|
|
|
|
TEST(traces_extract_http_info) {
|
|
cbm_trace_attr_t attrs[] = {
|
|
{.key = "http.method", .string_value = "GET"},
|
|
{.key = "http.route", .string_value = "/api/orders"},
|
|
{.key = "http.status_code", .string_value = "200"},
|
|
};
|
|
cbm_trace_span_t span = {
|
|
.kind = 2,
|
|
.attributes = attrs,
|
|
.attr_count = 3,
|
|
.start_time = "1000000000",
|
|
.end_time = "1050000000",
|
|
};
|
|
|
|
cbm_http_span_info_t info;
|
|
bool ok = cbm_extract_http_info(&span, "svc", &info);
|
|
ASSERT(ok);
|
|
ASSERT_STR_EQ(info.method, "GET");
|
|
ASSERT_STR_EQ(info.path, "/api/orders");
|
|
ASSERT_EQ(info.duration_ns, 50000000);
|
|
ASSERT_EQ(info.span_kind, 2);
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestExtractHTTPInfoNonHTTPSpan ─────────────────────────────── */
|
|
|
|
TEST(traces_extract_http_info_non_http) {
|
|
cbm_trace_attr_t attrs[] = {
|
|
{.key = "db.system", .string_value = "postgresql"},
|
|
};
|
|
cbm_trace_span_t span = {
|
|
.kind = 1,
|
|
.attributes = attrs,
|
|
.attr_count = 1,
|
|
};
|
|
|
|
cbm_http_span_info_t info;
|
|
bool ok = cbm_extract_http_info(&span, "svc", &info);
|
|
ASSERT(!ok);
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestExtractHTTPInfo with url.full ──────────────────────────── */
|
|
|
|
TEST(traces_extract_http_info_url_full) {
|
|
cbm_trace_attr_t attrs[] = {
|
|
{.key = "http.method", .string_value = "GET"},
|
|
{.key = "url.full", .string_value = "https://example.com/api/items?page=1"},
|
|
};
|
|
cbm_trace_span_t span = {
|
|
.kind = 2,
|
|
.attributes = attrs,
|
|
.attr_count = 2,
|
|
.start_time = "2000000000",
|
|
.end_time = "2100000000",
|
|
};
|
|
|
|
cbm_http_span_info_t info;
|
|
bool ok = cbm_extract_http_info(&span, "svc", &info);
|
|
ASSERT(ok);
|
|
ASSERT_STR_EQ(info.path, "/api/items");
|
|
ASSERT_EQ(info.duration_ns, 100000000);
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestExtractServiceName — edge cases ───────────────────────── */
|
|
|
|
TEST(traces_extract_service_name_empty_attrs) {
|
|
cbm_trace_resource_t r = {.attributes = NULL, .attr_count = 0};
|
|
const char *name = cbm_extract_service_name(&r);
|
|
ASSERT_STR_EQ(name, "");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_service_name_multiple_attrs) {
|
|
/* service.name is present but not the first attribute */
|
|
cbm_trace_attr_t attrs[] = {
|
|
{.key = "deployment.environment", .string_value = "production"},
|
|
{.key = "telemetry.sdk.language", .string_value = "go"},
|
|
{.key = "service.name", .string_value = "payment-gateway"},
|
|
};
|
|
cbm_trace_resource_t r = {.attributes = attrs, .attr_count = 3};
|
|
const char *name = cbm_extract_service_name(&r);
|
|
ASSERT_STR_EQ(name, "payment-gateway");
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestExtractPathFromURL ─────────────────────────────────────── */
|
|
|
|
TEST(traces_extract_path_from_url) {
|
|
char buf[256];
|
|
|
|
cbm_extract_path_from_url("https://example.com/api/orders", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "/api/orders");
|
|
|
|
cbm_extract_path_from_url("http://localhost:8080/health?check=true", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "/health");
|
|
|
|
cbm_extract_path_from_url("not-a-url", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "");
|
|
|
|
cbm_extract_path_from_url("https://example.com/", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "/");
|
|
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestExtractPathFromURL — edge cases ──────────────────────── */
|
|
|
|
TEST(traces_extract_path_null_url) {
|
|
char buf[256] = "untouched";
|
|
const char *result = cbm_extract_path_from_url(NULL, buf, sizeof(buf));
|
|
ASSERT_STR_EQ(result, "");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_path_empty_url) {
|
|
char buf[256];
|
|
cbm_extract_path_from_url("", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_path_with_port) {
|
|
char buf[256];
|
|
cbm_extract_path_from_url("https://api.example.com:8443/v2/users", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "/v2/users");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_path_with_query_and_fragment) {
|
|
char buf[256];
|
|
/* Query params should be stripped (stops at '?') */
|
|
cbm_extract_path_from_url("https://example.com/search?q=test&page=2", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "/search");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_path_buffer_too_small) {
|
|
char buf[4]; /* only 4 bytes — path "/api/orders" gets truncated */
|
|
cbm_extract_path_from_url("https://example.com/api/orders", buf, sizeof(buf));
|
|
/* Should truncate safely to 3 chars + NUL */
|
|
ASSERT_EQ(strlen(buf), 3);
|
|
ASSERT_STR_EQ(buf, "/ap");
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_extract_path_relative_path) {
|
|
/* No scheme — "relative/path" has fewer than 3 slashes → empty */
|
|
char buf[256];
|
|
cbm_extract_path_from_url("/api/orders", buf, sizeof(buf));
|
|
ASSERT_STR_EQ(buf, "");
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestCalculateP99 ───────────────────────────────────────────── */
|
|
|
|
TEST(traces_calculate_p99) {
|
|
int64_t values[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
|
|
int64_t p99 = cbm_calculate_p99(values, 10);
|
|
ASSERT_EQ(p99, 100);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_calculate_p99_single) {
|
|
int64_t values[] = {42};
|
|
int64_t p99 = cbm_calculate_p99(values, 1);
|
|
ASSERT_EQ(p99, 42);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_calculate_p99_empty) {
|
|
int64_t p99 = cbm_calculate_p99(NULL, 0);
|
|
ASSERT_EQ(p99, 0);
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestParseDuration ──────────────────────────────────────────── */
|
|
|
|
TEST(traces_parse_duration) {
|
|
int64_t d = cbm_parse_duration("1000000000", "1050000000");
|
|
ASSERT_EQ(d, 50000000);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_parse_duration_zero) {
|
|
int64_t d = cbm_parse_duration("1000", "500");
|
|
ASSERT_EQ(d, 0); /* end < start → 0 */
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestParseDuration — edge cases ──────────────────────────────── */
|
|
|
|
TEST(traces_parse_duration_null_start) {
|
|
int64_t d = cbm_parse_duration(NULL, "1000");
|
|
ASSERT_EQ(d, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_parse_duration_null_end) {
|
|
int64_t d = cbm_parse_duration("1000", NULL);
|
|
ASSERT_EQ(d, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_parse_duration_both_null) {
|
|
int64_t d = cbm_parse_duration(NULL, NULL);
|
|
ASSERT_EQ(d, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_parse_duration_equal) {
|
|
/* Same start and end → 0 */
|
|
int64_t d = cbm_parse_duration("5000000000", "5000000000");
|
|
ASSERT_EQ(d, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_parse_duration_large_values) {
|
|
/* Realistic nanosecond timestamps (epoch ~2024) */
|
|
int64_t d = cbm_parse_duration("1700000000000000000", "1700000000050000000");
|
|
ASSERT_EQ(d, 50000000); /* 50ms */
|
|
PASS();
|
|
}
|
|
|
|
/* ── TestCalculateP99 — edge cases ────────────────────────────────── */
|
|
|
|
TEST(traces_calculate_p99_100_values) {
|
|
int64_t values[100];
|
|
for (int i = 0; i < 100; i++) {
|
|
values[i] = (int64_t)(i + 1); /* 1..100 */
|
|
}
|
|
int64_t p99 = cbm_calculate_p99(values, 100);
|
|
/* idx = (int)(100 * 0.99) = 99, values[99] = 100 */
|
|
ASSERT_EQ(p99, 100);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_calculate_p99_reversed) {
|
|
/* Reversed array — qsort should sort first */
|
|
int64_t values[10] = {100, 90, 80, 70, 60, 50, 40, 30, 20, 10};
|
|
int64_t p99 = cbm_calculate_p99(values, 10);
|
|
/* After sort: 10..100, idx = (int)(10 * 0.99) = 9, values[9] = 100 */
|
|
ASSERT_EQ(p99, 100);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_calculate_p99_two_values) {
|
|
int64_t values[2] = {5, 500};
|
|
int64_t p99 = cbm_calculate_p99(values, 2);
|
|
/* idx = (int)(2 * 0.99) = 1, values[1] = 500 */
|
|
ASSERT_EQ(p99, 500);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_calculate_p99_all_same) {
|
|
int64_t values[5] = {42, 42, 42, 42, 42};
|
|
int64_t p99 = cbm_calculate_p99(values, 5);
|
|
ASSERT_EQ(p99, 42);
|
|
PASS();
|
|
}
|
|
|
|
TEST(traces_calculate_p99_negative_count) {
|
|
/* Negative count → treated like empty → 0 */
|
|
int64_t values[3] = {10, 20, 30};
|
|
int64_t p99 = cbm_calculate_p99(values, -1);
|
|
ASSERT_EQ(p99, 0);
|
|
PASS();
|
|
}
|
|
|
|
SUITE(traces) {
|
|
RUN_TEST(traces_extract_service_name);
|
|
RUN_TEST(traces_extract_service_name_missing);
|
|
RUN_TEST(traces_extract_service_name_null);
|
|
RUN_TEST(traces_extract_service_name_empty_attrs);
|
|
RUN_TEST(traces_extract_service_name_multiple_attrs);
|
|
RUN_TEST(traces_extract_http_info);
|
|
RUN_TEST(traces_extract_http_info_non_http);
|
|
RUN_TEST(traces_extract_http_info_url_full);
|
|
RUN_TEST(traces_extract_path_from_url);
|
|
RUN_TEST(traces_extract_path_null_url);
|
|
RUN_TEST(traces_extract_path_empty_url);
|
|
RUN_TEST(traces_extract_path_with_port);
|
|
RUN_TEST(traces_extract_path_with_query_and_fragment);
|
|
RUN_TEST(traces_extract_path_buffer_too_small);
|
|
RUN_TEST(traces_extract_path_relative_path);
|
|
RUN_TEST(traces_calculate_p99);
|
|
RUN_TEST(traces_calculate_p99_single);
|
|
RUN_TEST(traces_calculate_p99_empty);
|
|
RUN_TEST(traces_calculate_p99_100_values);
|
|
RUN_TEST(traces_calculate_p99_reversed);
|
|
RUN_TEST(traces_calculate_p99_two_values);
|
|
RUN_TEST(traces_calculate_p99_all_same);
|
|
RUN_TEST(traces_calculate_p99_negative_count);
|
|
RUN_TEST(traces_parse_duration);
|
|
RUN_TEST(traces_parse_duration_zero);
|
|
RUN_TEST(traces_parse_duration_null_start);
|
|
RUN_TEST(traces_parse_duration_null_end);
|
|
RUN_TEST(traces_parse_duration_both_null);
|
|
RUN_TEST(traces_parse_duration_equal);
|
|
RUN_TEST(traces_parse_duration_large_values);
|
|
}
|