diff options
Diffstat (limited to 'tests/api')
| -rw-r--r-- | tests/api/test-api.h | 186 | ||||
| -rw-r--r-- | tests/api/test-audit.c | 171 | ||||
| -rw-r--r-- | tests/api/test-buffer.c | 417 | ||||
| -rw-r--r-- | tests/api/test-bytecode.c | 643 | ||||
| -rw-r--r-- | tests/api/test-client.c | 334 | ||||
| -rw-r--r-- | tests/api/test-dependency.c | 551 | ||||
| -rw-r--r-- | tests/api/test-fileio.c | 236 | ||||
| -rw-r--r-- | tests/api/test-fragment.c | 376 | ||||
| -rw-r--r-- | tests/api/test-license.c | 311 | ||||
| -rw-r--r-- | tests/api/test-path-utils.c | 119 | ||||
| -rw-r--r-- | tests/api/test-tuple.c | 258 | ||||
| -rw-r--r-- | tests/api/test-variable.c | 247 |
12 files changed, 3849 insertions, 0 deletions
diff --git a/tests/api/test-api.h b/tests/api/test-api.h new file mode 100644 index 000000000000..cbe6598c846e --- /dev/null +++ b/tests/api/test-api.h @@ -0,0 +1,186 @@ +/* + * test-api.h + * test API macros and functions. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef TEST_API_H +#define TEST_API_H + +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include <libpkgconf/path.h> +#include <tests/win-shim.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpragmas" // older gcc may not know the next one + // Stop false-positive warnings for test cases +# pragma GCC diagnostic ignored "-Wanalyzer-tainted-assertion" +#endif + +#define TEST_FAIL_(fmt, ...) \ + do { \ + fprintf(stderr, "FAIL: %s:%d: " fmt "\n", \ + __FILE__, __LINE__, __VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while (0) + +#define TEST_ASSERT_TRUE(expr) \ + do { \ + if (!(expr)) \ + TEST_FAIL_("TEST_ASSERT_TRUE(%s) was false", #expr); \ + } while (0) + +#define TEST_ASSERT_FALSE(expr) \ + do { \ + if ((expr)) \ + TEST_FAIL_("TEST_ASSERT_FALSE(%s) was true", #expr); \ + } while (0) + +#define TEST_ASSERT_NULL(expr) \ + do { \ + const void *_v = (const void *)(expr); \ + if (_v != NULL) \ + TEST_FAIL_("TEST_ASSERT_NULL(%s) was %p", #expr, _v); \ + } while (0) + +#define TEST_ASSERT_NONNULL(expr) \ + do { \ + if ((expr) == NULL) \ + TEST_FAIL_("TEST_ASSERT_NONNULL(%s) was NULL", #expr); \ + } while (0) + +#define TEST_ASSERT_EQ(a, b) \ + do { \ + long long _a = (long long)(a); \ + long long _b = (long long)(b); \ + if (_a != _b) \ + TEST_FAIL_("TEST_ASSERT_EQ(%s, %s): %lld != %lld", \ + #a, #b, _a, _b); \ + } while (0) + +#define TEST_ASSERT_LT(a, b) \ + do { \ + long long _a = (long long)(a); \ + long long _b = (long long)(b); \ + if (_a >= _b) \ + TEST_FAIL_("TEST_ASSERT_LT(%s, %s): %lld >= %lld", \ + #a, #b, _a, _b); \ + } while (0) + +#define TEST_ASSERT_LE(a, b) \ + do { \ + long long _a = (long long)(a); \ + long long _b = (long long)(b); \ + if (_a > _b) \ + TEST_FAIL_("TEST_ASSERT_LE(%s, %s): %lld > %lld", \ + #a, #b, _a, _b); \ + } while (0) + +#define TEST_ASSERT_GT(a, b) \ + do { \ + long long _a = (long long)(a); \ + long long _b = (long long)(b); \ + if (_a <= _b) \ + TEST_FAIL_("TEST_ASSERT_GT(%s, %s): %lld <= %lld", \ + #a, #b, _a, _b); \ + } while (0) + +#define TEST_ASSERT_GE(a, b) \ + do { \ + long long _a = (long long)(a); \ + long long _b = (long long)(b); \ + if (_a < _b) \ + TEST_FAIL_("TEST_ASSERT_GE(%s, %s): %lld < %lld", \ + #a, #b, _a, _b); \ + } while (0) + +#define TEST_ASSERT_NE(a, b) \ + do { \ + long long _a = (long long)(a); \ + long long _b = (long long)(b); \ + if (_a == _b) \ + TEST_FAIL_("TEST_ASSERT_NE(%s, %s): both %lld", \ + #a, #b, _a); \ + } while (0) + +#define TEST_ASSERT_STRCMP_EQ(actual, expected) \ + do { \ + const char *_a = (actual); \ + const char *_e = (expected); \ + if (_a == NULL || _e == NULL || strcmp(_a, _e) != 0) \ + TEST_FAIL_("TEST_ASSERT_STRCMP_EQ(%s, %s): [%s] != [%s]", \ + #actual, #expected, \ + _a ? _a : "(null)", _e ? _e : "(null)"); \ + } while (0) + +#define TEST_ASSERT_STRCASECMP_EQ(actual, expected) \ + do { \ + const char *_a = (actual); \ + const char *_e = (expected); \ + if (_a == NULL || _e == NULL || strcasecmp(_a, _e) != 0) \ + TEST_FAIL_("TEST_ASSERT_STRCASECMP_EQ(%s, %s): [%s] !~ [%s]", \ + #actual, #expected, \ + _a ? _a : "(null)", _e ? _e : "(null)"); \ + } while (0) + +#define TEST_ASSERT_STRNCMP_EQ(actual, expected, n) \ + do { \ + const char *_a = (actual); \ + const char *_e = (expected); \ + size_t _n = (size_t)(n); \ + if (_a == NULL || _e == NULL || strncmp(_a, _e, _n) != 0) \ + TEST_FAIL_("TEST_ASSERT_STRNCMP_EQ(%s, %s, %zu): [%s] != [%s]", \ + #actual, #expected, _n, \ + _a ? _a : "(null)", _e ? _e : "(null)"); \ + } while (0) + +#define TEST_ASSERT_STRSTR(haystack, needle) \ + do { \ + const char *_h = (haystack); \ + const char *_n = (needle); \ + if (_h == NULL || _n == NULL || strstr(_h, _n) == NULL) \ + TEST_FAIL_("TEST_ASSERT_STRSTR(%s, %s): [%s] does not contain [%s]", \ + #haystack, #needle, \ + _h ? _h : "(null)", _n ? _n : "(null)"); \ + } while (0) + +#define TEST_ASSERT_EMPTY_STRING(expr) \ + do { \ + const char *_s = (expr); \ + if (_s == NULL) \ + TEST_FAIL_("TEST_ASSERT_EMPTY_STRING(%s) was NULL", #expr); \ + if (_s[0] != '\0') \ + TEST_FAIL_("TEST_ASSERT_EMPTY_STRING(%s) was [%s]", #expr, _s); \ + } while (0) + +#define TEST_RUN(name, fn) \ + do { \ + fprintf(stderr, "%s -> %s:", name, #fn); \ + fn(); \ + fprintf(stderr, " PASS\n"); \ + } while (0) + +static inline pkgconf_client_t * +test_client_new(void) +{ + pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default(); + return pkgconf_client_new(NULL, NULL, pers, NULL, NULL); +} + +#endif // TEST_API_H diff --git a/tests/api/test-audit.c b/tests/api/test-audit.c new file mode 100644 index 000000000000..12f9f31e0dda --- /dev/null +++ b/tests/api/test-audit.c @@ -0,0 +1,171 @@ +/* + * test-audit.c + * Tests for the public libpkgconf audit API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "test-api.h" + +/* + * Read the entire contents of f (from the start) into buf. + * buf must be large enough; we keep test inputs small. + */ +static void +slurp(FILE *f, char *buf, size_t bufsz) +{ + rewind(f); + size_t n = fread(buf, 1, bufsz - 1, f); + buf[n] = '\0'; +} + +static void +test_audit_log_no_logfile_is_noop(void) +{ + pkgconf_client_t *client = test_client_new(); + + // With no audit log set, logging must be a silent no-op and must not crash. + pkgconf_audit_log(client, "should go nowhere\n"); + + pkgconf_client_free(client); +} + +static void +test_audit_set_log_and_write(void) +{ + pkgconf_client_t *client = test_client_new(); + FILE *logf = tmpfile(); + TEST_ASSERT_NONNULL(logf); + + pkgconf_audit_set_log(client, logf); + pkgconf_audit_log(client, "hello %s\n", "world"); + + char buf[256]; + slurp(logf, buf, sizeof(buf)); + TEST_ASSERT_STRCMP_EQ(buf, "hello world\n"); + + fclose(logf); + pkgconf_client_free(client); +} + +static void +test_audit_log_multiple_writes(void) +{ + pkgconf_client_t *client = test_client_new(); + FILE *logf = tmpfile(); + TEST_ASSERT_NONNULL(logf); + + pkgconf_audit_set_log(client, logf); + pkgconf_audit_log(client, "first\n"); + pkgconf_audit_log(client, "second %d\n", 2); + + char buf[256]; + slurp(logf, buf, sizeof(buf)); + TEST_ASSERT_STRCMP_EQ(buf, "first\nsecond 2\n"); + + fclose(logf); + pkgconf_client_free(client); +} + +static void +test_audit_log_dependency_versionless(void) +{ + pkgconf_client_t *client = test_client_new(); + FILE *logf = tmpfile(); + TEST_ASSERT_NONNULL(logf); + + pkgconf_audit_set_log(client, logf); + + // A minimal package: only the fields the logger reads + pkgconf_pkg_t pkg = { 0 }; + pkg.id = (char *) "foo"; + pkg.version = (char *) "1.2.3"; + + /* A dependency with PKGCONF_CMP_ANY and no version: the logger + * should emit only "id [version]", skipping the comparator */ + pkgconf_dependency_t dep = { 0 }; + dep.compare = PKGCONF_CMP_ANY; + dep.version = NULL; + + pkgconf_audit_log_dependency(client, &pkg, &dep); + + char buf[256]; + slurp(logf, buf, sizeof(buf)); + TEST_ASSERT_STRCMP_EQ(buf, "foo [1.2.3]\n"); + + fclose(logf); + pkgconf_client_free(client); +} + +static void +test_audit_log_dependency_versioned(void) +{ + pkgconf_client_t *client = test_client_new(); + FILE *logf = tmpfile(); + TEST_ASSERT_NONNULL(logf); + + pkgconf_audit_set_log(client, logf); + + pkgconf_pkg_t pkg = { 0 }; + pkg.id = (char *) "bar"; + pkg.version = (char *) "2.0"; + + /* version set AND compare != ANY: the logger emits the + * comparator and required version between id and [version] */ + pkgconf_dependency_t dep = { 0 }; + dep.compare = PKGCONF_CMP_GREATER_THAN_EQUAL; + dep.version = (char *) "1.5"; + + pkgconf_audit_log_dependency(client, &pkg, &dep); + + char buf[256]; + slurp(logf, buf, sizeof(buf)); + TEST_ASSERT_STRCMP_EQ(buf, "bar >= 1.5 [2.0]\n"); + + fclose(logf); + pkgconf_client_free(client); +} + +static void +test_audit_log_dependency_no_logfile_is_noop(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_pkg_t pkg = { 0 }; + pkg.id = (char *) "foo"; + pkg.version = (char *) "1.0"; + + pkgconf_dependency_t dep = { 0 }; + dep.compare = PKGCONF_CMP_ANY; + + // No log set: must be a silent no-op + pkgconf_audit_log_dependency(client, &pkg, &dep); + + pkgconf_client_free(client); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_audit_log_no_logfile_is_noop); + TEST_RUN(basename, test_audit_set_log_and_write); + TEST_RUN(basename, test_audit_log_multiple_writes); + TEST_RUN(basename, test_audit_log_dependency_versionless); + TEST_RUN(basename, test_audit_log_dependency_versioned); + TEST_RUN(basename, test_audit_log_dependency_no_logfile_is_noop); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-buffer.c b/tests/api/test-buffer.c new file mode 100644 index 000000000000..c17818ba3b4b --- /dev/null +++ b/tests/api/test-buffer.c @@ -0,0 +1,417 @@ +/* + * test-buffer.c + * Tests for libpkgconf buffer API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "test-api.h" + +static void +test_buffer_empty(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0); + TEST_ASSERT_NULL(pkgconf_buffer_str(&buf)); + TEST_ASSERT_EMPTY_STRING(pkgconf_buffer_str_or_empty(&buf)); + TEST_ASSERT_EQ(pkgconf_buffer_lastc(&buf), '\0'); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_append(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_buffer_append(&buf, "hello")); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello"); + TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 5); + + TEST_ASSERT_TRUE(pkgconf_buffer_append(&buf, " world")); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello world"); + TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 11); + + TEST_ASSERT_EQ(pkgconf_buffer_lastc(&buf), 'd'); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_append_slice(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_buffer_append_slice(&buf, "abcdefgh", 3)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "abc"); + + TEST_ASSERT_TRUE(pkgconf_buffer_append_slice(&buf, "xyz", 0)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "abc"); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_append_fmt(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_buffer_append_fmt(&buf, "%s=%d", "x", 42)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "x=42"); + + TEST_ASSERT_TRUE(pkgconf_buffer_append_fmt(&buf, " %s", "ok")); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "x=42 ok"); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_prepend(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&buf, "world"); + TEST_ASSERT_TRUE(pkgconf_buffer_prepend(&buf, "hello ")); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello world"); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_push_byte(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_buffer_push_byte(&buf, 'a')); + TEST_ASSERT_TRUE(pkgconf_buffer_push_byte(&buf, 'b')); + TEST_ASSERT_TRUE(pkgconf_buffer_push_byte(&buf, 'c')); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "abc"); + TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 3); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_trim_byte(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&buf, "hello\n"); + TEST_ASSERT_TRUE(pkgconf_buffer_trim_byte(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello"); + + pkgconf_buffer_reset(&buf); + pkgconf_buffer_push_byte(&buf, 'x'); + TEST_ASSERT_TRUE(pkgconf_buffer_trim_byte(&buf)); + TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0); + TEST_ASSERT_FALSE(pkgconf_buffer_trim_byte(&buf)); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_reset(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&buf, "some content"); + TEST_ASSERT_NE(pkgconf_buffer_len(&buf), 0); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0); + TEST_ASSERT_NULL(pkgconf_buffer_str(&buf)); + + TEST_ASSERT_TRUE(pkgconf_buffer_append(&buf, "fresh")); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "fresh"); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_freeze(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&buf, "frozen"); + char *out = pkgconf_buffer_freeze(&buf); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "frozen"); + TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0); + + TEST_ASSERT_NULL(pkgconf_buffer_freeze(&buf)); + + free(out); + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_copy(void) +{ + pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&src, "original"); + pkgconf_buffer_append(&dst, "to be replaced"); + TEST_ASSERT_TRUE(pkgconf_buffer_copy(&src, &dst)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst), "original"); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&src), "original"); // src is unchanged + + pkgconf_buffer_finalize(&src); + pkgconf_buffer_finalize(&dst); +} + +static void +test_buffer_join(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_buffer_join(&buf, '/', "usr", "local", "lib", NULL)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "usr/local/lib"); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_contains(void) +{ + pkgconf_buffer_t hay = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t needle = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&hay, "the quick brown fox"); + + pkgconf_buffer_append(&needle, "quick"); + TEST_ASSERT_TRUE(pkgconf_buffer_contains(&hay, &needle)); + + pkgconf_buffer_reset(&needle); + pkgconf_buffer_append(&needle, "slow"); + TEST_ASSERT_FALSE(pkgconf_buffer_contains(&hay, &needle)); + + TEST_ASSERT_TRUE(pkgconf_buffer_contains_byte(&hay, 'q')); + TEST_ASSERT_FALSE(pkgconf_buffer_contains_byte(&hay, 'z')); + + pkgconf_buffer_finalize(&hay); + pkgconf_buffer_finalize(&needle); +} + +static void +test_buffer_match(void) +{ + pkgconf_buffer_t a = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t b = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&a, "identical"); + pkgconf_buffer_append(&b, "identical"); + TEST_ASSERT_TRUE(pkgconf_buffer_match(&a, &b)); + + // Identical length, but different contents + pkgconf_buffer_reset(&b); + pkgconf_buffer_append(&b, "different"); + TEST_ASSERT_FALSE(pkgconf_buffer_match(&a, &b)); + + // Different length and contents + pkgconf_buffer_reset(&b); + pkgconf_buffer_append(&b, "different!"); + TEST_ASSERT_FALSE(pkgconf_buffer_match(&a, &b)); + + pkgconf_buffer_finalize(&a); + pkgconf_buffer_finalize(&b); +} + +static void +test_buffer_has_prefix(void) +{ + pkgconf_buffer_t hay = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t pre = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&hay, "/usr/local/lib"); + + pkgconf_buffer_append(&pre, "/usr"); + TEST_ASSERT_TRUE(pkgconf_buffer_has_prefix(&hay, &pre)); + + pkgconf_buffer_reset(&pre); + pkgconf_buffer_append(&pre, "/opt"); + TEST_ASSERT_FALSE(pkgconf_buffer_has_prefix(&hay, &pre)); + + pkgconf_buffer_finalize(&hay); + pkgconf_buffer_finalize(&pre); +} + +static void +test_buffer_subst(void) +{ + pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&src, "prefix=${PREFIX}/share"); + TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst, &src, "${PREFIX}", "/opt/foo")); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst), "prefix=/opt/foo/share"); + + pkgconf_buffer_finalize(&src); + pkgconf_buffer_finalize(&dst); +} + +static void +test_buffer_subst_empty_pattern(void) +{ + pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst0 = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst1 = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst2 = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst3 = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst4 = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst5 = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&src, "prefix=${PREFIX}/share"); + + TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst0, &src, "", "foo")); + TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst1, &src, "", "")); + TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst2, &src, "", NULL)); + TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst3, &src, NULL, "foo")); + TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst4, &src, NULL, "")); + TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst5, &src, NULL, NULL)); + + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst0), "prefix=${PREFIX}/share"); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst1), "prefix=${PREFIX}/share"); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst2), "prefix=${PREFIX}/share"); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst3), "prefix=${PREFIX}/share"); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst4), "prefix=${PREFIX}/share"); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst5), "prefix=${PREFIX}/share"); + + pkgconf_buffer_finalize(&src); + pkgconf_buffer_finalize(&dst0); + pkgconf_buffer_finalize(&dst1); + pkgconf_buffer_finalize(&dst2); + pkgconf_buffer_finalize(&dst3); + pkgconf_buffer_finalize(&dst4); + pkgconf_buffer_finalize(&dst5); +} + +static void +test_buffer_escape(void) +{ + pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t dst = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_span_t spans[] = { + { ' ', ' ' }, + { '\t', '\t' }, + }; + + pkgconf_buffer_append(&src, "a b\tc"); + TEST_ASSERT_TRUE(pkgconf_buffer_escape(&dst, &src, spans, PKGCONF_ARRAY_SIZE(spans))); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst), "a\\ b\\\tc"); + + pkgconf_buffer_finalize(&src); + pkgconf_buffer_finalize(&dst); +} + +static void +test_str_eq_slice(void) +{ + TEST_ASSERT_TRUE(pkgconf_str_eq_slice("hello", "hello", 5)); + TEST_ASSERT_FALSE(pkgconf_str_eq_slice("hello!", "hello", 5)); + TEST_ASSERT_FALSE(pkgconf_str_eq_slice("hello", "world", 5)); + TEST_ASSERT_FALSE(pkgconf_str_eq_slice(NULL, "hello", 5)); +} + +static void +test_span_contains(void) +{ + pkgconf_span_t spans[] = { + { '0', '9' }, + { 'a', 'z' }, + }; + + TEST_ASSERT_TRUE(pkgconf_span_contains('5', spans, PKGCONF_ARRAY_SIZE(spans))); + TEST_ASSERT_TRUE(pkgconf_span_contains('m', spans, PKGCONF_ARRAY_SIZE(spans))); + TEST_ASSERT_FALSE(pkgconf_span_contains('A', spans, PKGCONF_ARRAY_SIZE(spans))); + TEST_ASSERT_FALSE(pkgconf_span_contains('!', spans, PKGCONF_ARRAY_SIZE(spans))); +} + +static void +test_buffer_fputs_nonempty(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = tmpfile(); + TEST_ASSERT_NONNULL(f); + + pkgconf_buffer_append(&buf, "hello world"); + TEST_ASSERT_TRUE(pkgconf_buffer_fputs(&buf, f)); + + char out[64]; + rewind(f); + size_t n = fread(out, 1, sizeof(out) - 1, f); + out[n] = '\0'; + + // Buffer contents followed by a newline. + TEST_ASSERT_STRCMP_EQ(out, "hello world\n"); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_buffer_fputs_empty(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = tmpfile(); + TEST_ASSERT_NONNULL(f); + + // An empty buffer writes only a newline. + TEST_ASSERT_TRUE(pkgconf_buffer_fputs(&buf, f)); + + char out[64]; + rewind(f); + size_t n = fread(out, 1, sizeof(out) - 1, f); + out[n] = '\0'; + + TEST_ASSERT_STRCMP_EQ(out, "\n"); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +int +main(int argc, const char **argv) +{ + (void) argc; + + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_buffer_empty); + TEST_RUN(basename, test_buffer_append); + TEST_RUN(basename, test_buffer_append_slice); + TEST_RUN(basename, test_buffer_append_fmt); + TEST_RUN(basename, test_buffer_prepend); + TEST_RUN(basename, test_buffer_push_byte); + TEST_RUN(basename, test_buffer_trim_byte); + TEST_RUN(basename, test_buffer_reset); + TEST_RUN(basename, test_buffer_freeze); + TEST_RUN(basename, test_buffer_copy); + TEST_RUN(basename, test_buffer_join); + TEST_RUN(basename, test_buffer_contains); + TEST_RUN(basename, test_buffer_match); + TEST_RUN(basename, test_buffer_has_prefix); + TEST_RUN(basename, test_buffer_subst); + TEST_RUN(basename, test_buffer_subst_empty_pattern); + TEST_RUN(basename, test_buffer_escape); + TEST_RUN(basename, test_str_eq_slice); + TEST_RUN(basename, test_span_contains); + TEST_RUN(basename, test_buffer_fputs_nonempty); + TEST_RUN(basename, test_buffer_fputs_empty); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-bytecode.c b/tests/api/test-bytecode.c new file mode 100644 index 000000000000..7be48941eed9 --- /dev/null +++ b/tests/api/test-bytecode.c @@ -0,0 +1,643 @@ +/* + * test-bytecode.c + * Tests for the public libpkgconf bytecode API: + * pkgconf_bytecode_compile and pkgconf_bytecode_eval_str. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "test-api.h" + +/* + * Build a variable list with the given key/value pairs. Caller frees + * with pkgconf_variable_list_free(). + * + * The key/value pairs are stored by compiling `value` as bytecode and + * stashing the result on a pkgconf_variable_t inside the list, which + * is how the parser builds variable scopes for real .pc files. + */ +static void +seed_variable(pkgconf_list_t *vars, const char *key, const char *value) +{ + pkgconf_variable_t *v = pkgconf_variable_get_or_create(vars, key); + TEST_ASSERT_NONNULL(v); + + pkgconf_buffer_reset(&v->bcbuf); + pkgconf_bytecode_compile(&v->bcbuf, value); + pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf); +} + +static void +test_emit_text_and_eval(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_t bc; + bool saw_sysroot = false; + + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "plain", 5)); + pkgconf_bytecode_from_buffer(&bc, &bcbuf); + + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "plain"); + + pkgconf_buffer_finalize(&out); + pkgconf_buffer_finalize(&bcbuf); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_emit_guards(void) +{ + pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, NULL, 5)); + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "x", 0)); + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, NULL, 3)); + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, "x", 0)); + + TEST_ASSERT_EQ(pkgconf_buffer_len(&bcbuf), 0); + + pkgconf_buffer_finalize(&bcbuf); +} + +static void +test_emit_var_and_eval(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_t bc; + bool saw_sysroot = false; + + seed_variable(&vars, "name", "world"); + + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "hello ", 6)); + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, "name", 4)); + pkgconf_bytecode_from_buffer(&bc, &bcbuf); + + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "hello world"); + + pkgconf_buffer_finalize(&out); + pkgconf_buffer_finalize(&bcbuf); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_emit_sysroot_and_eval(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_t bc; + bool saw_sysroot = false; + + pkgconf_client_set_sysroot_dir(client, "/sysroot"); + + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_sysroot(&bcbuf)); + TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "/usr/include", 12)); + pkgconf_bytecode_from_buffer(&bc, &bcbuf); + + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot)); + TEST_ASSERT_TRUE(saw_sysroot); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "/sysroot/usr/include"); + + pkgconf_buffer_finalize(&out); + pkgconf_buffer_finalize(&bcbuf); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_null_args(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_t bc = { NULL, 0 }; + + // NULL client, bc, or out all return false. + TEST_ASSERT_FALSE(pkgconf_bytecode_eval(NULL, &vars, &bc, &out, NULL)); + TEST_ASSERT_FALSE(pkgconf_bytecode_eval(client, &vars, NULL, &out, NULL)); + TEST_ASSERT_FALSE(pkgconf_bytecode_eval(client, &vars, &bc, NULL, NULL)); + + pkgconf_buffer_finalize(&out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_compile_null_args(void) +{ + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + + TEST_ASSERT_FALSE(pkgconf_bytecode_compile(NULL, "x")); + TEST_ASSERT_FALSE(pkgconf_bytecode_compile(&out, NULL)); + + pkgconf_buffer_finalize(&out); +} + +static void +test_dollar_escape(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // $$ collapses to a literal $; the following text is NOT treated as a variable reference + char *out = pkgconf_bytecode_eval_str(client, &vars, "price: $$5 and $${notavar}", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "price: $5 and ${notavar}"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_malformed_unclosed(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // A ${ with no closing } is emitted as literal text + char *out = pkgconf_bytecode_eval_str(client, &vars, "broken ${unclosed", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "broken ${unclosed"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_empty_braces(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // ${} has a zero-length name; it's emitted as literal text rather than treated as a variable + char *out = pkgconf_bytecode_eval_str(client, &vars, "empty ${} here", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "empty ${} here"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_compile_time_sysroot(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + pkgconf_client_set_sysroot_dir(client, "/sysroot"); + + // ${pc_sysrootdir} is special-cased at compile time into an OP_SYSROOT op rather than a variable lookup + char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_TRUE(saw_sysroot); + TEST_ASSERT_STRCMP_EQ(out, "/sysroot/usr/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_sysroot_dot_disables(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // A sysroot of "." disables sysroot rewriting: ${pc_sysrootdir} expands to empty + pkgconf_client_set_sysroot_dir(client, "."); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "/usr/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_sysroot_root_disables(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // A sysroot of "/" (the root dir) disables rewriting. + pkgconf_client_set_sysroot_dir(client, "/"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "/usr/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_sysroot_trailing_slash_trimmed(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // Trailing slashes on the sysroot are normalized away, so the result doesn't get a doubled slash + pkgconf_client_set_sysroot_dir(client, "/sysroot///"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_TRUE(saw_sysroot); + TEST_ASSERT_STRCMP_EQ(out, "/sysroot/usr/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_sysroot_normalizes_to_root_disables(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + /* A sysroot of "//" survives the bare-"/" early check but normalizes down to "/" after + * trailing-slash trimming, which then disables rewriting (empty sysroot) */ + pkgconf_client_set_sysroot_dir(client, "//"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "/usr/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_circular_reference(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // A variable that references itself must not infinite-loop: the `expanding` guard breaks the cycle + seed_variable(&vars, "loop", "${loop}"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${loop}", &saw_sysroot); + + /* The self-reference is broken; behavior is "expands to nothing further" + * We mostly care that it returns rather than hanging or crashing */ + if (out != NULL) + free(out); + + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_references_var(void) +{ + pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_bytecode_compile(&bcbuf, "-I${includedir} -L${libdir}"); + + TEST_ASSERT_TRUE(pkgconf_bytecode_references_var(&bcbuf, "includedir")); + TEST_ASSERT_TRUE(pkgconf_bytecode_references_var(&bcbuf, "libdir")); + TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, "prefix")); + + TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(NULL, "x")); + TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, NULL)); + + pkgconf_buffer_finalize(&bcbuf); +} + +static void +test_references_var_text_only(void) +{ + pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_bytecode_compile(&bcbuf, "just plain text"); + + TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, "anything")); + + pkgconf_buffer_finalize(&bcbuf); +} + +static void +test_rewrite_selfrefs(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + pkgconf_buffer_t prev = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_compile(&prev, "old"); + + pkgconf_buffer_t rhs = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_compile(&rhs, "${foo} new"); + + pkgconf_buffer_t rewritten = PKGCONF_BUFFER_INITIALIZER; + TEST_ASSERT_TRUE(pkgconf_bytecode_rewrite_selfrefs(&rewritten, &rhs, "foo", &prev)); + + pkgconf_bytecode_t bc; + pkgconf_bytecode_from_buffer(&bc, &rewritten); + + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "old new"); + + pkgconf_buffer_finalize(&out); + pkgconf_buffer_finalize(&rewritten); + pkgconf_buffer_finalize(&rhs); + pkgconf_buffer_finalize(&prev); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_rewrite_selfrefs_no_match(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + pkgconf_buffer_t prev = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_compile(&prev, "PREV"); + + pkgconf_buffer_t rhs = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_compile(&rhs, "${other} tail"); + + pkgconf_buffer_t rewritten = PKGCONF_BUFFER_INITIALIZER; + TEST_ASSERT_TRUE(pkgconf_bytecode_rewrite_selfrefs(&rewritten, &rhs, "foo", &prev)); + + // ${other} survives; seed it so eval can resolve it + seed_variable(&vars, "other", "OTHER"); + + pkgconf_bytecode_t bc; + pkgconf_bytecode_from_buffer(&bc, &rewritten); + + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "OTHER tail"); + + pkgconf_buffer_finalize(&out); + pkgconf_buffer_finalize(&rewritten); + pkgconf_buffer_finalize(&rhs); + pkgconf_buffer_finalize(&prev); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_plain_text(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // A string with no variable references should round-trip unchanged. + char *out = pkgconf_bytecode_eval_str(client, &vars, "plain text value", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "plain text value"); + TEST_ASSERT_FALSE(saw_sysroot); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_variable_substitution(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + seed_variable(&vars, "prefix", "/opt/foo"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${prefix}/lib", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "/opt/foo/lib"); + TEST_ASSERT_FALSE(saw_sysroot); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_nested_variables(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // Recursive expansion: libdir references prefix. + seed_variable(&vars, "prefix", "/usr/local"); + seed_variable(&vars, "libdir", "${prefix}/lib"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "-L${libdir}", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "-L/usr/local/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_undefined_variable(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // Referencing an undefined variable should produce empty substitution, not failure + char *out = pkgconf_bytecode_eval_str(client, &vars, "prefix=${nonexistent}/end", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "prefix=/end"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_multiple_variables(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + seed_variable(&vars, "prefix", "/usr"); + seed_variable(&vars, "exec_prefix", "/usr/local"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "-I${prefix}/include -L${exec_prefix}/lib", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "-I/usr/include -L/usr/local/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_empty_input(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + char *out = pkgconf_bytecode_eval_str(client, &vars, "", &saw_sysroot); + + // An empty input may evaluate to either NULL or an empty string + if (out != NULL) + { + TEST_ASSERT_STRCMP_EQ(out, ""); + free(out); + } + + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_eval_sysroot_detection(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + pkgconf_client_set_sysroot_dir(client, "/sysroot"); + + seed_variable(&vars, "pc_sysrootdir", "/sysroot"); + seed_variable(&vars, "includedir", "${pc_sysrootdir}/usr/include"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${includedir}", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_TRUE(saw_sysroot); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_compile_produces_nonempty_buffer(void) +{ + pkgconf_buffer_t bc = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_bytecode_compile(&bc, "hello ${world}"); + + // The compiled bytecode buffer should be non-empty + TEST_ASSERT_NE(pkgconf_buffer_len(&bc), 0); + + pkgconf_buffer_finalize(&bc); +} + +static void +test_compile_eval_roundtrip(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // Compile directly, then evaluate via the bc field on a variable + seed_variable(&vars, "name", "world"); + + pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "greeting"); + TEST_ASSERT_NONNULL(v); + pkgconf_buffer_reset(&v->bcbuf); + pkgconf_bytecode_compile(&v->bcbuf, "hello ${name}"); + pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf); + + char *out = pkgconf_variable_eval_str(client, &vars, v, &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "hello world"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_eval_plain_text); + TEST_RUN(basename, test_eval_empty_input); + TEST_RUN(basename, test_eval_variable_substitution); + TEST_RUN(basename, test_eval_nested_variables); + TEST_RUN(basename, test_eval_multiple_variables); + TEST_RUN(basename, test_eval_undefined_variable); + TEST_RUN(basename, test_eval_sysroot_detection); + TEST_RUN(basename, test_eval_null_args); + + TEST_RUN(basename, test_emit_guards); + TEST_RUN(basename, test_emit_text_and_eval); + TEST_RUN(basename, test_emit_var_and_eval); + TEST_RUN(basename, test_emit_sysroot_and_eval); + + TEST_RUN(basename, test_dollar_escape); + TEST_RUN(basename, test_malformed_unclosed); + TEST_RUN(basename, test_empty_braces); + + TEST_RUN(basename, test_circular_reference); + TEST_RUN(basename, test_references_var); + TEST_RUN(basename, test_references_var_text_only); + + TEST_RUN(basename, test_compile_time_sysroot); + TEST_RUN(basename, test_sysroot_dot_disables); + TEST_RUN(basename, test_sysroot_root_disables); + TEST_RUN(basename, test_sysroot_trailing_slash_trimmed); + TEST_RUN(basename, test_sysroot_normalizes_to_root_disables); + + TEST_RUN(basename, test_rewrite_selfrefs); + TEST_RUN(basename, test_rewrite_selfrefs_no_match); + + TEST_RUN(basename, test_compile_eval_roundtrip); + TEST_RUN(basename, test_compile_produces_nonempty_buffer); + TEST_RUN(basename, test_compile_null_args); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-client.c b/tests/api/test-client.c new file mode 100644 index 000000000000..ae8889b658c8 --- /dev/null +++ b/tests/api/test-client.c @@ -0,0 +1,334 @@ +/* + * test-client.c + * Tests for the public libpkgconf client API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "test-api.h" + +static void +test_client_new_and_free(void) +{ + pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default(); + pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, NULL); + TEST_ASSERT_NONNULL(client); + + pkgconf_client_free(client); +} + +static void +test_client_init_and_deinit_stack(void) +{ + /* Caller-allocated client, the path the CLI itself uses + * (pkgconf_cli_state_t embeds a pkgconf_client_t by value) */ + pkgconf_client_t client = { 0 }; + pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default(); + + pkgconf_client_init(&client, NULL, NULL, pers, NULL, NULL); + pkgconf_client_deinit(&client); +} + +static void +test_client_sysroot_dir(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_client_set_sysroot_dir(client, "/tmp/sysroot"); + TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_sysroot_dir(client), "/tmp/sysroot"); + + pkgconf_client_set_sysroot_dir(client, "/opt/sysroot"); + TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_sysroot_dir(client), "/opt/sysroot"); + + pkgconf_client_free(client); +} + +static void +test_client_buildroot_dir(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_client_set_buildroot_dir(client, "/tmp/buildroot"); + TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_buildroot_dir(client), "/tmp/buildroot"); + + pkgconf_client_free(client); +} + +static void +test_client_flags(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_client_set_flags(client, PKGCONF_PKG_PKGF_NO_CACHE | PKGCONF_PKG_PKGF_SKIP_CONFLICTS); + + unsigned int flags = pkgconf_client_get_flags(client); + TEST_ASSERT_NE(flags & PKGCONF_PKG_PKGF_NO_CACHE, 0); + TEST_ASSERT_NE(flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS, 0); + TEST_ASSERT_EQ(flags & PKGCONF_PKG_PKGF_ENV_ONLY, 0); + + pkgconf_client_free(client); +} + +static void +test_client_prefix_varname(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_client_set_prefix_varname(client, "prefix"); + TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_prefix_varname(client), "prefix"); + + pkgconf_client_set_prefix_varname(client, "custom_prefix"); + TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_prefix_varname(client), "custom_prefix"); + + pkgconf_client_free(client); +} + +/* + * Capture buffer for handler tests. Each handler writes the message + * into this buffer so the test can verify it fired with the expected + * content. Reset before each test that uses it. + */ +static char capture_buf[256]; + +static bool +capture_handler(const char *msg, const pkgconf_client_t *client, void *data) +{ + (void) client; + (void) data; + + strncpy(capture_buf, msg, sizeof(capture_buf) - 1); + capture_buf[sizeof(capture_buf) - 1] = '\0'; + return true; +} + +static void +capture_reset(void) +{ + memset(capture_buf, 0, sizeof(capture_buf)); +} + +static void +test_client_error_handler_fires(void) +{ + pkgconf_client_t *client = test_client_new(); + capture_reset(); + + pkgconf_client_set_error_handler(client, capture_handler, NULL); + + TEST_ASSERT_EQ(pkgconf_client_get_error_handler(client), capture_handler); + + pkgconf_error(client, "test error: %d", 42); + + TEST_ASSERT_NE(capture_buf[0], '\0'); + TEST_ASSERT_STRSTR(capture_buf, "test error: 42"); + + pkgconf_client_free(client); +} + +static void +test_client_warn_handler_fires(void) +{ + pkgconf_client_t *client = test_client_new(); + capture_reset(); + + pkgconf_client_set_warn_handler(client, capture_handler, NULL); + + TEST_ASSERT_EQ(pkgconf_client_get_warn_handler(client), capture_handler); + + pkgconf_warn(client, "test warning: %s", "hello"); + + TEST_ASSERT_NE(capture_buf[0], '\0'); + TEST_ASSERT_STRSTR(capture_buf, "test warning: hello"); + + pkgconf_client_free(client); +} + +#ifndef PKGCONF_LITE +static void +test_client_trace_handler_fires(void) +{ + pkgconf_client_t *client = test_client_new(); + capture_reset(); + + pkgconf_client_set_trace_handler(client, capture_handler, NULL); + + TEST_ASSERT_EQ(pkgconf_client_get_trace_handler(client), capture_handler); + + PKGCONF_TRACE(client, "trace message: %d", 7); + + TEST_ASSERT_NE(capture_buf[0], '\0'); + TEST_ASSERT_STRSTR(capture_buf, "trace message: 7"); + + pkgconf_client_free(client); +} +#endif + +static void +unveil_capture_handler(const pkgconf_client_t *client, const char *path, const char *permissions) +{ + (void) client; + (void) permissions; + + if (path != NULL) + { + strncpy(capture_buf, path, sizeof(capture_buf) - 1); + capture_buf[sizeof(capture_buf) - 1] = '\0'; + } +} + +static void +test_client_unveil_handler_installation(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_client_set_unveil_handler(client, unveil_capture_handler); + TEST_ASSERT_EQ(pkgconf_client_get_unveil_handler(client), unveil_capture_handler); + + pkgconf_client_free(client); +} + +/* + * Custom environ handler that returns canned values for known keys + * and NULL otherwise. + */ +static const char * +canned_environ_handler(const pkgconf_client_t *client, const char *key) +{ + (void) client; + + if (!strcmp(key, "PKG_TEST_VAR")) + return "the_value"; + if (!strcmp(key, "EMPTY_VAR")) + return ""; + if (!strcmp(key, "PKG_CONFIG_SYSTEM_INCLUDE_PATH")) + return "/custom/include"; + if (!strcmp(key, "PKG_CONFIG_SYSTEM_LIBRARY_PATH")) + return "/custom/lib"; + + return NULL; +} + +static void +test_client_getenv_via_handler(void) +{ + pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default(); + pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, canned_environ_handler); + TEST_ASSERT_NONNULL(client); + + const char *v = pkgconf_client_getenv(client, "PKG_TEST_VAR"); + TEST_ASSERT_NONNULL(v); + TEST_ASSERT_STRCMP_EQ(v, "the_value"); + + v = pkgconf_client_getenv(client, "EMPTY_VAR"); + TEST_ASSERT_NONNULL(v); + TEST_ASSERT_STRCMP_EQ(v, ""); + + v = pkgconf_client_getenv(client, "UNDEFINED_VAR"); + TEST_ASSERT_NULL(v); + + pkgconf_client_free(client); +} + +static void +test_client_dir_list_build_smoke(void) +{ + pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default(); + pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, NULL); + TEST_ASSERT_NONNULL(client); + + pkgconf_client_dir_list_build(client, pers); + + /* The personality's default dir list comes from PKG_DEFAULT_PATH + * (set at compile time). After build, the client should have + * SOME directories registered; we don't assert specific paths + * since they vary by build configuration. */ + TEST_ASSERT_NONNULL(client->dir_list.head); + + pkgconf_client_free(client); +} + +static void +test_client_init_system_paths_from_environ(void) +{ + pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default(); + pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, canned_environ_handler); + TEST_ASSERT_NONNULL(client); + + /* With PKG_CONFIG_SYSTEM_{INCLUDE,LIBRARY}_PATH set, init builds the filter dirs from the + * environment instead of copying the personality's defaults. */ + TEST_ASSERT_TRUE(pkgconf_path_match_list("/custom/include", &client->filter_includedirs)); + TEST_ASSERT_TRUE(pkgconf_path_match_list("/custom/lib", &client->filter_libdirs)); + + pkgconf_client_free(client); +} + +static void +test_client_preload_from_environ(void) +{ + pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default(); + pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, NULL); + TEST_ASSERT_NONNULL(client); + + /* preload_from_environ reads the named var via getenv directly. + * Point it at a dir; preload_path will try to load .pc files from there. + * An empty/nonexistent dir is fine; we're exercising the split-and-iterate path, not asserting + * loads. */ + setenv("PKG_TEST_PRELOAD", "/nonexistent/dir", 1); + + pkgconf_client_preload_from_environ(client, "PKG_TEST_PRELOAD"); + + unsetenv("PKG_TEST_PRELOAD"); + pkgconf_client_free(client); +} + +#ifndef PKGCONF_LITE +static void +test_client_trace_null_client(void) +{ + TEST_ASSERT_FALSE(pkgconf_trace(NULL, "test.c", 42, "func", "msg %d", 42)); +} +#endif + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_client_new_and_free); + TEST_RUN(basename, test_client_init_and_deinit_stack); + TEST_RUN(basename, test_client_init_system_paths_from_environ); + TEST_RUN(basename, test_client_preload_from_environ); + + TEST_RUN(basename, test_client_sysroot_dir); + TEST_RUN(basename, test_client_buildroot_dir); + TEST_RUN(basename, test_client_flags); + TEST_RUN(basename, test_client_prefix_varname); + + TEST_RUN(basename, test_client_error_handler_fires); + TEST_RUN(basename, test_client_warn_handler_fires); +#ifndef PKGCONF_LITE + TEST_RUN(basename, test_client_trace_handler_fires); +#endif + TEST_RUN(basename, test_client_unveil_handler_installation); + + TEST_RUN(basename, test_client_getenv_via_handler); + + TEST_RUN(basename, test_client_dir_list_build_smoke); + +#ifndef PKGCONF_LITE + TEST_RUN(basename, test_client_trace_null_client); +#endif + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-dependency.c b/tests/api/test-dependency.c new file mode 100644 index 000000000000..edd00b620f18 --- /dev/null +++ b/tests/api/test-dependency.c @@ -0,0 +1,551 @@ +/* + * test-dependency.c + * Tests for the public libpkgconf dependency API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "test-api.h" + +static size_t +dependency_count(const pkgconf_list_t *list) +{ + size_t n = 0; + const pkgconf_node_t *iter; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + n++; + } + + return n; +} + +static const pkgconf_dependency_t * +dependency_at(const pkgconf_list_t *list, size_t index) +{ + const pkgconf_node_t *iter; + size_t i = 0; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + if (i++ == index) + return iter->data; + } + + return NULL; +} + +static void +test_comparator_lookup_known(void) +{ + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("="), PKGCONF_CMP_EQUAL); + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("!="), PKGCONF_CMP_NOT_EQUAL); + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("<"), PKGCONF_CMP_LESS_THAN); + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("<="), PKGCONF_CMP_LESS_THAN_EQUAL); + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name(">"), PKGCONF_CMP_GREATER_THAN); + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name(">="), PKGCONF_CMP_GREATER_THAN_EQUAL); +} + +static void +test_comparator_lookup_unknown(void) +{ + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("~~"), PKGCONF_CMP_ANY); + TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name(""), PKGCONF_CMP_ANY); +} + +static void +test_comparator_roundtrip(void) +{ + pkgconf_pkg_comparator_t values[] = + { + PKGCONF_CMP_NOT_EQUAL, + PKGCONF_CMP_ANY, + PKGCONF_CMP_LESS_THAN, + PKGCONF_CMP_LESS_THAN_EQUAL, + PKGCONF_CMP_EQUAL, + PKGCONF_CMP_GREATER_THAN, + PKGCONF_CMP_GREATER_THAN_EQUAL, + }; + + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(values); i++) + { + pkgconf_dependency_t dep = { 0 }; + dep.compare = values[i]; + + const char *str = pkgconf_pkg_get_comparator(&dep); + TEST_ASSERT_NONNULL(str); + + // ANY's value does not round-trip so do not check + if (values[i] == PKGCONF_CMP_ANY) + continue; + + pkgconf_pkg_comparator_t back = pkgconf_pkg_comparator_lookup_by_name(str); + TEST_ASSERT_EQ(back, values[i]); + } +} + +static void +test_parse_str_single(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_parse_str(client, &deps, "foo", 0); + + TEST_ASSERT_EQ(dependency_count(&deps), 1); + const pkgconf_dependency_t *d = dependency_at(&deps, 0); + TEST_ASSERT_NONNULL(d); + TEST_ASSERT_STRCMP_EQ(d->package, "foo"); + TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_ANY); + TEST_ASSERT_NULL(d->version); + + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_parse_str_versioned(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_parse_str(client, &deps, "foo >= 1.2.3", 0); + + TEST_ASSERT_EQ(dependency_count(&deps), 1); + const pkgconf_dependency_t *d = dependency_at(&deps, 0); + TEST_ASSERT_NONNULL(d); + TEST_ASSERT_STRCMP_EQ(d->package, "foo"); + TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_GREATER_THAN_EQUAL); + TEST_ASSERT_STRCMP_EQ(d->version, "1.2.3"); + + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_parse_str_multiple_space_separated(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_parse_str(client, &deps, "foo bar baz", 0); + + TEST_ASSERT_EQ(dependency_count(&deps), 3); + + const pkgconf_dependency_t *d0 = dependency_at(&deps, 0); + const pkgconf_dependency_t *d1 = dependency_at(&deps, 1); + const pkgconf_dependency_t *d2 = dependency_at(&deps, 2); + TEST_ASSERT_NONNULL(d0); + TEST_ASSERT_NONNULL(d1); + TEST_ASSERT_NONNULL(d2); + TEST_ASSERT_STRCMP_EQ(d0->package, "foo"); + TEST_ASSERT_STRCMP_EQ(d1->package, "bar"); + TEST_ASSERT_STRCMP_EQ(d2->package, "baz"); + + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_parse_str_multiple_comma_separated(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_parse_str(client, &deps, "foo, bar, baz", 0); + + TEST_ASSERT_EQ(dependency_count(&deps), 3); + + const pkgconf_dependency_t *d0 = dependency_at(&deps, 0); + const pkgconf_dependency_t *d1 = dependency_at(&deps, 1); + const pkgconf_dependency_t *d2 = dependency_at(&deps, 2); + TEST_ASSERT_NONNULL(d0); + TEST_ASSERT_NONNULL(d1); + TEST_ASSERT_NONNULL(d2); + TEST_ASSERT_STRCMP_EQ(d0->package, "foo"); + TEST_ASSERT_STRCMP_EQ(d1->package, "bar"); + TEST_ASSERT_STRCMP_EQ(d2->package, "baz"); + + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_parse_str_mixed_versioned_and_bare(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_parse_str(client, &deps, "foo >= 1.0, bar, baz < 2.0", 0); + + TEST_ASSERT_EQ(dependency_count(&deps), 3); + + const pkgconf_dependency_t *d0 = dependency_at(&deps, 0); + const pkgconf_dependency_t *d1 = dependency_at(&deps, 1); + const pkgconf_dependency_t *d2 = dependency_at(&deps, 2); + + TEST_ASSERT_NONNULL(d0); + TEST_ASSERT_NONNULL(d1); + TEST_ASSERT_NONNULL(d2); + + TEST_ASSERT_STRCMP_EQ(d0->package, "foo"); + TEST_ASSERT_EQ(d0->compare, PKGCONF_CMP_GREATER_THAN_EQUAL); + TEST_ASSERT_STRCMP_EQ(d0->version, "1.0"); + + TEST_ASSERT_STRCMP_EQ(d1->package, "bar"); + TEST_ASSERT_EQ(d1->compare, PKGCONF_CMP_ANY); + + TEST_ASSERT_STRCMP_EQ(d2->package, "baz"); + TEST_ASSERT_EQ(d2->compare, PKGCONF_CMP_LESS_THAN); + TEST_ASSERT_STRCMP_EQ(d2->version, "2.0"); + + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_parse_str_empty(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_parse_str(client, &deps, "", 0); + TEST_ASSERT_EQ(dependency_count(&deps), 0); + + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_parse_str_all_comparators(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_parse_str(client, &deps, "a = 1, b != 2, c < 3, d <= 4, e > 5, f >= 6", 0); + + TEST_ASSERT_EQ(dependency_count(&deps), 6); + + const pkgconf_dependency_t *d0 = dependency_at(&deps, 0); + const pkgconf_dependency_t *d1 = dependency_at(&deps, 1); + const pkgconf_dependency_t *d2 = dependency_at(&deps, 2); + const pkgconf_dependency_t *d3 = dependency_at(&deps, 3); + const pkgconf_dependency_t *d4 = dependency_at(&deps, 4); + const pkgconf_dependency_t *d5 = dependency_at(&deps, 5); + + TEST_ASSERT_NONNULL(d0); + TEST_ASSERT_NONNULL(d1); + TEST_ASSERT_NONNULL(d2); + TEST_ASSERT_NONNULL(d3); + TEST_ASSERT_NONNULL(d4); + TEST_ASSERT_NONNULL(d5); + + TEST_ASSERT_EQ(d0->compare, PKGCONF_CMP_EQUAL); + TEST_ASSERT_EQ(d1->compare, PKGCONF_CMP_NOT_EQUAL); + TEST_ASSERT_EQ(d2->compare, PKGCONF_CMP_LESS_THAN); + TEST_ASSERT_EQ(d3->compare, PKGCONF_CMP_LESS_THAN_EQUAL); + TEST_ASSERT_EQ(d4->compare, PKGCONF_CMP_GREATER_THAN); + TEST_ASSERT_EQ(d5->compare, PKGCONF_CMP_GREATER_THAN_EQUAL); + + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_dependency_add(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_t *d = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_GREATER_THAN_EQUAL, 0); + TEST_ASSERT_NONNULL(d); + TEST_ASSERT_STRCMP_EQ(d->package, "foo"); + TEST_ASSERT_STRCMP_EQ(d->version, "1.0"); + TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_GREATER_THAN_EQUAL); + + TEST_ASSERT_EQ(dependency_count(&deps), 1); + + pkgconf_dependency_unref(client, d); + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_dependency_add_no_version(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_t *d = pkgconf_dependency_add(client, &deps, "foo", NULL, PKGCONF_CMP_ANY, 0); + TEST_ASSERT_NONNULL(d); + TEST_ASSERT_STRCMP_EQ(d->package, "foo"); + TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_ANY); + + pkgconf_dependency_unref(client, d); + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_dependency_add_multiple(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_t *d0 = pkgconf_dependency_add(client, &deps, "foo", NULL, PKGCONF_CMP_ANY, 0); + pkgconf_dependency_t *d1 = pkgconf_dependency_add(client, &deps, "bar", "2.0", PKGCONF_CMP_EQUAL, 0); + pkgconf_dependency_t *d2 = pkgconf_dependency_add(client, &deps, "baz", "3.0", PKGCONF_CMP_LESS_THAN, 0); + + TEST_ASSERT_NONNULL(d0); + TEST_ASSERT_NONNULL(d1); + TEST_ASSERT_NONNULL(d2); + + TEST_ASSERT_EQ(dependency_count(&deps), 3); + + pkgconf_dependency_unref(client, d0); + pkgconf_dependency_unref(client, d1); + pkgconf_dependency_unref(client, d2); + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_dependency_collision_drops_flagged_newcomer(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_t *first = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, 0); + TEST_ASSERT_NONNULL(first); + TEST_ASSERT_EQ(dependency_count(&deps), 1); + + /* Adding the same dep WITH flags collides; the flagged newcomer + * is dropped in favour of the existing unflagged node, so _add + * returns NULL and the count stays at 1 */ + pkgconf_dependency_t *second = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, PKGCONF_PKG_DEPF_INTERNAL); + TEST_ASSERT_NULL(second); + TEST_ASSERT_EQ(dependency_count(&deps), 1); + + pkgconf_dependency_unref(client, first); + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_dependency_collision_drops_flagged_existing(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER; + + pkgconf_dependency_t *first = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, PKGCONF_PKG_DEPF_INTERNAL); + TEST_ASSERT_NONNULL(first); + TEST_ASSERT_EQ(dependency_count(&deps), 1); + + /* Adding the same dep UNFLAGGED collides; the existing flagged + * node is deleted and unref'd, the unflagged newcomer takes its + * place; count stays at 1, but the node is now the new one */ + pkgconf_dependency_t *second = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, 0); + TEST_ASSERT_NONNULL(second); + TEST_ASSERT_EQ(dependency_count(&deps), 1); + TEST_ASSERT_EQ(second->flags, 0); + + pkgconf_dependency_unref(client, first); + pkgconf_dependency_unref(client, second); + pkgconf_dependency_free(&deps); + pkgconf_client_free(client); +} + +static void +test_version_equal(void) +{ + TEST_ASSERT_EQ(pkgconf_compare_version("1.0", "1.0"), 0); + TEST_ASSERT_EQ(pkgconf_compare_version("1.2.3", "1.2.3"), 0); + TEST_ASSERT_EQ(pkgconf_compare_version("", ""), 0); +} + +static void +test_version_simple_numeric(void) +{ + TEST_ASSERT_LT(pkgconf_compare_version("1.0", "1.1"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.1", "1.0"), 0); + + TEST_ASSERT_LT(pkgconf_compare_version("1.0", "2.0"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("2.0", "1.0"), 0); + + TEST_ASSERT_LT(pkgconf_compare_version("1.2.3", "1.2.4"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.2.3", "1.3.0"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.2.3", "2.0.0"), 0); +} + +static void +test_version_numeric_segments_not_lexical(void) +{ + TEST_ASSERT_GT(pkgconf_compare_version("1.10", "1.9"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.100", "1.99"), 0); +} + +static void +test_version_different_lengths(void) +{ + TEST_ASSERT_LT(pkgconf_compare_version("1.0", "1.0.1"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0.1", "1.0"), 0); +} + +static void +test_version_alpha_suffix(void) +{ + TEST_ASSERT_GT(pkgconf_compare_version("1.0a", "1.0"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0alpha", "1.0"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0rc1", "1.0"), 0); + + TEST_ASSERT_LT(pkgconf_compare_version("1.0alpha", "1.0beta"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.0beta", "1.0rc"), 0); +} + +static void +test_version_tilde_prerelease(void) +{ + TEST_ASSERT_LT(pkgconf_compare_version("1.0~rc1", "1.0"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.0~alpha", "1.0"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.0~beta", "1.0~rc"), 0); + + TEST_ASSERT_LT(pkgconf_compare_version("1.0~rc1", "1.0rc1"), 0); +} + +static void +test_version_numeric_beats_alpha(void) +{ + TEST_ASSERT_GT(pkgconf_compare_version("1.0.1", "1.0a"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.0a", "1.0.1"), 0); +} + +static void +test_version_alpha_ordering(void) +{ + TEST_ASSERT_LT(pkgconf_compare_version("1.0a", "1.0b"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0b", "1.0a"), 0); +} + +static void +test_version_dotted_vs_hyphenated(void) +{ + TEST_ASSERT_EQ(pkgconf_compare_version("1.0-1", "1.0.1"), 0); +} + +static void +test_version_leading_zeros(void) +{ + TEST_ASSERT_EQ(pkgconf_compare_version("1.01", "1.1"), 0); + TEST_ASSERT_EQ(pkgconf_compare_version("01.0", "1.0"), 0); +} + +static void +test_version_trailing_zero_segments(void) +{ + /* Pkgconf does NOT treat "1.0" and "1.0.0" as equivalent. + * Even a zero trailing numeric segment is additional content that makes the version greater. + */ + TEST_ASSERT_LT(pkgconf_compare_version("1.0", "1.0.0"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0.0", "1.0"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1", "1.0"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1", "1.0.0.0"), 0); +} + +static void +test_version_null_handling(void) +{ + TEST_ASSERT_LT(pkgconf_compare_version(NULL, "1.0"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0", NULL), 0); + TEST_ASSERT_LT(pkgconf_compare_version(NULL, NULL), 0); +} + +static void +test_version_tilde_both_sides(void) +{ + TEST_ASSERT_LT(pkgconf_compare_version("1.0~", "1.0a"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0a", "1.0~"), 0); + + TEST_ASSERT_EQ(pkgconf_compare_version("1.0~rc", "1.0~rc"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.0~a", "1.0~b"), 0); + TEST_ASSERT_LT(pkgconf_compare_version("1.0~1", "1.0~2"), 0); +} + +static void +test_version_separator_equivalence(void) +{ + TEST_ASSERT_EQ(pkgconf_compare_version("1.0", "1-0"), 0); + TEST_ASSERT_EQ(pkgconf_compare_version("1.2.3", "1_2_3"), 0); + TEST_ASSERT_EQ(pkgconf_compare_version("1..2", "1.2"), 0); +} + +static void +test_version_case_insensitive(void) +{ + TEST_ASSERT_EQ(pkgconf_compare_version("1.0RC1", "1.0rc1"), 0); + TEST_ASSERT_EQ(pkgconf_compare_version("1.0A", "1.0a"), 0); +} + +static void +test_version_alpha_prefix(void) +{ + TEST_ASSERT_LT(pkgconf_compare_version("1.0alpha", "1.0alphabeta"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0alphabeta", "1.0alpha"), 0); + + TEST_ASSERT_LT(pkgconf_compare_version("1.0a", "1.0ab"), 0); + TEST_ASSERT_GT(pkgconf_compare_version("1.0ab", "1.0a"), 0); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_comparator_lookup_known); + TEST_RUN(basename, test_comparator_lookup_unknown); + TEST_RUN(basename, test_comparator_roundtrip); + + TEST_RUN(basename, test_parse_str_empty); + TEST_RUN(basename, test_parse_str_single); + TEST_RUN(basename, test_parse_str_versioned); + TEST_RUN(basename, test_parse_str_multiple_space_separated); + TEST_RUN(basename, test_parse_str_multiple_comma_separated); + TEST_RUN(basename, test_parse_str_mixed_versioned_and_bare); + TEST_RUN(basename, test_parse_str_all_comparators); + + TEST_RUN(basename, test_dependency_add); + TEST_RUN(basename, test_dependency_add_no_version); + TEST_RUN(basename, test_dependency_add_multiple); + TEST_RUN(basename, test_dependency_collision_drops_flagged_newcomer); + TEST_RUN(basename, test_dependency_collision_drops_flagged_existing); + + TEST_RUN(basename, test_version_equal); + TEST_RUN(basename, test_version_simple_numeric); + TEST_RUN(basename, test_version_numeric_segments_not_lexical); + TEST_RUN(basename, test_version_different_lengths); + TEST_RUN(basename, test_version_alpha_suffix); + TEST_RUN(basename, test_version_tilde_prerelease); + TEST_RUN(basename, test_version_numeric_beats_alpha); + TEST_RUN(basename, test_version_alpha_ordering); + TEST_RUN(basename, test_version_dotted_vs_hyphenated); + TEST_RUN(basename, test_version_leading_zeros); + TEST_RUN(basename, test_version_trailing_zero_segments); + TEST_RUN(basename, test_version_null_handling); + TEST_RUN(basename, test_version_tilde_both_sides); + TEST_RUN(basename, test_version_separator_equivalence); + TEST_RUN(basename, test_version_case_insensitive); + TEST_RUN(basename, test_version_alpha_prefix); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-fileio.c b/tests/api/test-fileio.c new file mode 100644 index 000000000000..a4d5f81679a6 --- /dev/null +++ b/tests/api/test-fileio.c @@ -0,0 +1,236 @@ +/* + * test-fileio.c + * Tests for the public libpkgconf file i/o API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "test-api.h" + +static FILE * +fmemstream(const char *contents) +{ + FILE *f = tmpfile(); + + TEST_ASSERT_NONNULL(f); + + fwrite(contents, 1, strlen(contents), f); + rewind(f); + + return f; +} + +static void +test_fgetline_no_trailing_newline(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("hello"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_fgetline_empty_stream(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream(""); + + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_fgetline_lf(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("hello\nworld\n"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_fgetline_crlf(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("hello\r\nworld\r\n"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_fgetline_lone_cr(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("hello\rworld\r"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_fgetline_backslash_continuation_lf(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("foo\\\nbar\n"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar"); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_fgetline_backslash_continuation_crlf(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("foo\\\r\nbar\r\n"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +static void +test_fgetline_backslash_continuation_lone_cr(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("foo\\\rbar\r"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +// A backslash not immediately followed by a newline is not a continuation, +// so it must be preserved literally in the output. +static void +test_fgetline_backslash_not_continuation(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + FILE *f = fmemstream("foo\\bar\n"); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foo\\bar"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +// fgets() only stops on '\n', a full read buffer, or EOF. NOT on on a lone '\r'. +// If a '\r' happens to be the very last byte fgets() manages to read +// before the buffer fills up, the matching '\n' is still unread in the +// stream and isn't visible to pkgconf_fgetline()'s lookahead, so the CRLF +// pair gets split across two fgets() calls and is misparsed as two lines. +static void +test_fgetline_crlf_split_across_fgets_buffer(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + size_t prefix_len = PKGCONF_ITEM_SIZE - 2; + char *content = malloc(prefix_len + strlen("\r\nworld\n") + 1); + FILE *f; + + TEST_ASSERT_NONNULL(content); + memset(content, 'a', prefix_len); + strcpy(content + prefix_len, "\r\nworld\n"); + + f = fmemstream(content); + free(content); + + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_EQ(strlen(pkgconf_buffer_str(&buf)), prefix_len); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world"); + + pkgconf_buffer_reset(&buf); + TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f)); + + fclose(f); + pkgconf_buffer_finalize(&buf); +} + +int +main(int argc, const char **argv) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_fgetline_no_trailing_newline); + TEST_RUN(basename, test_fgetline_empty_stream); + TEST_RUN(basename, test_fgetline_lf); + TEST_RUN(basename, test_fgetline_crlf); + TEST_RUN(basename, test_fgetline_lone_cr); + TEST_RUN(basename, test_fgetline_backslash_continuation_lf); + TEST_RUN(basename, test_fgetline_backslash_continuation_crlf); + TEST_RUN(basename, test_fgetline_backslash_continuation_lone_cr); + TEST_RUN(basename, test_fgetline_backslash_not_continuation); + TEST_RUN(basename, test_fgetline_crlf_split_across_fgets_buffer); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-fragment.c b/tests/api/test-fragment.c new file mode 100644 index 000000000000..3d49145ae518 --- /dev/null +++ b/tests/api/test-fragment.c @@ -0,0 +1,376 @@ +/* + * test-fragment.c + * Tests for the public libpkgconf fragment API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "test-api.h" + +static size_t +fragment_count(const pkgconf_list_t *list) +{ + size_t n = 0; + const pkgconf_node_t *iter; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + n++; + } + + return n; +} + +static const pkgconf_fragment_t * +fragment_at(const pkgconf_list_t *list, size_t index) +{ + const pkgconf_node_t *iter; + size_t i = 0; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + if (i++ == index) + return iter->data; + } + + return NULL; +} + +/* + * Render a fragment list to a newly-allocated C string for assertions. + * Caller frees. + */ +static char * +render_to_string(const pkgconf_list_t *list) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_fragment_render_buf(list, &buf, false, NULL, ' '); + + if (pkgconf_buffer_str(&buf) == NULL) + return strdup(""); + + char *out = strdup(pkgconf_buffer_str(&buf)); + pkgconf_buffer_finalize(&buf); + return out; +} + +static void +test_fragment_parse_cflags(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_fragment_parse(client, &frags, &vars, "-I/usr/include -DFOO=1", 0)); + TEST_ASSERT_EQ(fragment_count(&frags), 2); + + const pkgconf_fragment_t *first = fragment_at(&frags, 0); + TEST_ASSERT_NONNULL(first); + TEST_ASSERT_EQ(first->type, 'I'); + TEST_ASSERT_STRCMP_EQ(first->data, "/usr/include"); + + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_parse_libs(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_fragment_parse(client, &frags, &vars, "-L/usr/lib -lfoo -lbar", 0)); + TEST_ASSERT_EQ(fragment_count(&frags), 3); + + const pkgconf_fragment_t *f0 = fragment_at(&frags, 0); + const pkgconf_fragment_t *f1 = fragment_at(&frags, 1); + const pkgconf_fragment_t *f2 = fragment_at(&frags, 2); + + TEST_ASSERT_NONNULL(f0); + TEST_ASSERT_NONNULL(f1); + TEST_ASSERT_NONNULL(f2); + + TEST_ASSERT_EQ(f0->type, 'L'); + TEST_ASSERT_STRCMP_EQ(f0->data, "/usr/lib"); + TEST_ASSERT_EQ(f1->type, 'l'); + TEST_ASSERT_STRCMP_EQ(f1->data, "foo"); + TEST_ASSERT_EQ(f2->type, 'l'); + TEST_ASSERT_STRCMP_EQ(f2->data, "bar"); + + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_parse_empty(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + TEST_ASSERT_TRUE(pkgconf_fragment_parse(client, &frags, &vars, "", 0)); + TEST_ASSERT_EQ(fragment_count(&frags), 0); + + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_add_single(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_fragment_add(client, &frags, &vars, "-I/opt/include", 0); + + TEST_ASSERT_EQ(fragment_count(&frags), 1); + + const pkgconf_fragment_t *f = fragment_at(&frags, 0); + TEST_ASSERT_NONNULL(f); + TEST_ASSERT_EQ(f->type, 'I'); + TEST_ASSERT_STRCMP_EQ(f->data, "/opt/include"); + + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_render_cflags(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_fragment_parse(client, &frags, &vars, "-I/usr/include -I/opt/include", 0); + + char *rendered = render_to_string(&frags); + TEST_ASSERT_NONNULL(rendered); + TEST_ASSERT_STRCMP_EQ(rendered, "-I/usr/include -I/opt/include"); + + free(rendered); + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_render_libs(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_fragment_parse(client, &frags, &vars, "-L/usr/lib -lfoo", 0); + + char *rendered = render_to_string(&frags); + TEST_ASSERT_NONNULL(rendered); + TEST_ASSERT_STRCMP_EQ(rendered, "-L/usr/lib -lfoo"); + + free(rendered); + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_render_empty(void) +{ + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + + char *rendered = render_to_string(&frags); + TEST_ASSERT_NONNULL(rendered); + TEST_ASSERT_EMPTY_STRING(rendered); + + free(rendered); +} + +// Filter predicate: keep only -I (include) fragments. +static bool +filter_only_includes(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + (void) client; + (void) data; + return frag->type == 'I'; +} + +// Filter predicate: keep only -l (library name) fragments. +static bool +filter_only_libnames(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + (void) client; + (void) data; + return frag->type == 'l'; +} + +// Filter predicate: keep nothing. +static bool +filter_nothing(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + (void) client; + (void) frag; + (void) data; + return false; +} + +static void +test_fragment_filter_only_includes(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t src = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_fragment_parse(client, &src, &vars, "-I/usr/include -L/usr/lib -lfoo -I/opt/include", 0); + + pkgconf_fragment_filter(client, &dst, &src, filter_only_includes, NULL); + TEST_ASSERT_EQ(fragment_count(&dst), 2); + + const pkgconf_fragment_t *f0 = fragment_at(&dst, 0); + const pkgconf_fragment_t *f1 = fragment_at(&dst, 1); + TEST_ASSERT_NONNULL(f0); + TEST_ASSERT_NONNULL(f1); + TEST_ASSERT_EQ(f0->type, 'I'); + TEST_ASSERT_STRCMP_EQ(f0->data, "/usr/include"); + TEST_ASSERT_EQ(f1->type, 'I'); + TEST_ASSERT_STRCMP_EQ(f1->data, "/opt/include"); + + pkgconf_fragment_free(&dst); + pkgconf_fragment_free(&src); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_filter_only_libnames(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t src = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_fragment_parse(client, &src, &vars, "-L/usr/lib -lfoo -lbar -I/usr/include", 0); + + pkgconf_fragment_filter(client, &dst, &src, filter_only_libnames, NULL); + + TEST_ASSERT_EQ(fragment_count(&dst), 2); + + const pkgconf_fragment_t *f0 = fragment_at(&dst, 0); + const pkgconf_fragment_t *f1 = fragment_at(&dst, 1); + TEST_ASSERT_NONNULL(f0); + TEST_ASSERT_NONNULL(f1); + TEST_ASSERT_EQ(f0->type, 'l'); + TEST_ASSERT_STRCMP_EQ(f0->data, "foo"); + TEST_ASSERT_EQ(f1->type, 'l'); + TEST_ASSERT_STRCMP_EQ(f1->data, "bar"); + + pkgconf_fragment_free(&dst); + pkgconf_fragment_free(&src); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_filter_keeps_nothing(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t src = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_fragment_parse(client, &src, &vars, "-I/usr/include -lfoo", 0); + + pkgconf_fragment_filter(client, &dst, &src, filter_nothing, NULL); + + TEST_ASSERT_EQ(fragment_count(&dst), 0); + + pkgconf_fragment_free(&dst); + pkgconf_fragment_free(&src); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_has_system_dir_matches(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_path_add("/usr/include", &client->filter_includedirs, false); + + pkgconf_fragment_parse(client, &frags, &vars, "-I/usr/include -I/opt/include", 0); + + const pkgconf_fragment_t *system = fragment_at(&frags, 0); + const pkgconf_fragment_t *other = fragment_at(&frags, 1); + TEST_ASSERT_NONNULL(system); + TEST_ASSERT_NONNULL(other); + + TEST_ASSERT_TRUE(pkgconf_fragment_has_system_dir(client, system)); + TEST_ASSERT_FALSE(pkgconf_fragment_has_system_dir(client, other)); + + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_fragment_has_system_dir_libs(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_path_add("/usr/lib", &client->filter_libdirs, false); + + pkgconf_fragment_parse(client, &frags, &vars, "-L/usr/lib -L/opt/lib", 0); + + const pkgconf_fragment_t *system = fragment_at(&frags, 0); + const pkgconf_fragment_t *other = fragment_at(&frags, 1); + TEST_ASSERT_NONNULL(system); + TEST_ASSERT_NONNULL(other); + + TEST_ASSERT_TRUE(pkgconf_fragment_has_system_dir(client, system)); + TEST_ASSERT_FALSE(pkgconf_fragment_has_system_dir(client, other)); + + pkgconf_fragment_free(&frags); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_fragment_parse_empty); + TEST_RUN(basename, test_fragment_parse_cflags); + TEST_RUN(basename, test_fragment_parse_libs); + TEST_RUN(basename, test_fragment_add_single); + TEST_RUN(basename, test_fragment_render_empty); + TEST_RUN(basename, test_fragment_render_cflags); + TEST_RUN(basename, test_fragment_render_libs); + TEST_RUN(basename, test_fragment_filter_only_includes); + TEST_RUN(basename, test_fragment_filter_only_libnames); + TEST_RUN(basename, test_fragment_filter_keeps_nothing); + TEST_RUN(basename, test_fragment_has_system_dir_matches); + TEST_RUN(basename, test_fragment_has_system_dir_libs); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-license.c b/tests/api/test-license.c new file mode 100644 index 000000000000..d77ed21a4c86 --- /dev/null +++ b/tests/api/test-license.c @@ -0,0 +1,311 @@ +/* + * test-license.c + * Tests for the public libpkgconf license API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "test-api.h" + +static size_t +license_count(const pkgconf_list_t *list) +{ + size_t n = 0; + const pkgconf_node_t *iter; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + n++; + } + + return n; +} + +static char * +render_to_string(pkgconf_client_t *client, const pkgconf_list_t *list) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_license_render(client, list, &buf); + + char *out = strdup(pkgconf_buffer_str_or_empty(&buf)); + pkgconf_buffer_finalize(&buf); + return out; +} + +static void +test_license_insert_and_free(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_insert(client, &licenses, PKGCONF_LICENSE_EXPRESSION, "BSD-3-Clause"); + TEST_ASSERT_EQ(license_count(&licenses), 1); + + const pkgconf_license_t *l = licenses.head->data; + TEST_ASSERT_NONNULL(l); + TEST_ASSERT_EQ(l->type, PKGCONF_LICENSE_EXPRESSION); + TEST_ASSERT_STRCMP_EQ(l->data, "BSD-3-Clause"); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_free_empty(void) +{ + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + // Freeing an empty list is a no-op. Smoke test. + pkgconf_license_free(&licenses); + + // Smoke test + pkgconf_license_free(NULL); +} + +static void +test_license_evaluate_single(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &licenses, "BSD-3-Clause", 0); + + TEST_ASSERT_EQ(license_count(&licenses), 1); + const pkgconf_license_t *l = licenses.head->data; + TEST_ASSERT_EQ(l->type, PKGCONF_LICENSE_EXPRESSION); + TEST_ASSERT_STRCMP_EQ(l->data, "BSD-3-Clause"); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_or(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &licenses, "MIT OR ISC", 0); + + TEST_ASSERT_EQ(license_count(&licenses), 3); + + char *rendered = render_to_string(client, &licenses); + TEST_ASSERT_STRCMP_EQ(rendered, "MIT OR ISC"); + free(rendered); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_and(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &licenses, "LGPL-2.1-only AND MIT", 0); + + char *rendered = render_to_string(client, &licenses); + TEST_ASSERT_STRCMP_EQ(rendered, "LGPL-2.1-only AND MIT"); + free(rendered); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_multiple_keys_implicit_and(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &licenses, "BSD-3-Clause", 0); + pkgconf_license_evaluate_str(client, &licenses, "BSD-2-Clause", 0); + + char *rendered = render_to_string(client, &licenses); + TEST_ASSERT_STRCMP_EQ(rendered, "BSD-3-Clause AND BSD-2-Clause"); + free(rendered); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_brackets(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &licenses, "ISC AND (BSD-3-Clause AND BSD-2-Clause)", 0); + + char *rendered = render_to_string(client, &licenses); + TEST_ASSERT_STRCMP_EQ(rendered, "ISC AND (BSD-3-Clause AND BSD-2-Clause)"); + free(rendered); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_empty(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &licenses, "", 0); + TEST_ASSERT_EQ(license_count(&licenses), 0); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_sanitizes(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + /* The sanitiser strips characters outside the allowed set + * (alnum, '-', '+', '(', ')', '.', ':'). A token of pure + * junk should sanitise to empty and be skipped. */ + pkgconf_license_evaluate_str(client, &licenses, "BSD-3-Clause", 0); + + const pkgconf_license_t *l = licenses.head->data; + TEST_ASSERT_STRCMP_EQ(l->data, "BSD-3-Clause"); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_render_empty(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + char *rendered = render_to_string(client, &licenses); + TEST_ASSERT_EMPTY_STRING(rendered); + free(rendered); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_render_single(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_insert(client, &licenses, PKGCONF_LICENSE_EXPRESSION, "MIT"); + + char *rendered = render_to_string(client, &licenses); + TEST_ASSERT_STRCMP_EQ(rendered, "MIT"); + free(rendered); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_copy_list(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t src = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &src, "MIT OR ISC", 0); + size_t src_count = license_count(&src); + + pkgconf_license_copy_list(client, &dst, &src); + TEST_ASSERT_EQ(license_count(&dst), src_count); + + /* The copy is independent: freeing the source must not affect + * the destination's rendered output. */ + pkgconf_license_free(&src); + + char *rendered = render_to_string(client, &dst); + TEST_ASSERT_STRCMP_EQ(rendered, "MIT OR ISC"); + free(rendered); + + pkgconf_license_free(&dst); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_long_sanitized_token(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + char token[512]; + token[0] = 'A'; + memset(token + 1, '_', 400); + token[401] = '\0'; + + pkgconf_license_evaluate_str(client, &licenses, token, 0); + + TEST_ASSERT_EQ(license_count(&licenses), 1); + const pkgconf_license_t *l = licenses.head->data; + TEST_ASSERT_EQ(l->type, PKGCONF_LICENSE_EXPRESSION); + TEST_ASSERT_STRCMP_EQ(l->data, "A"); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +static void +test_license_evaluate_unterminated_quote(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER; + + pkgconf_license_evaluate_str(client, &licenses, "\"", 0); + TEST_ASSERT_EQ(license_count(&licenses), 0); + + pkgconf_license_evaluate_str(client, &licenses, "MIT \"unterminated", 0); + TEST_ASSERT_EQ(license_count(&licenses), 0); + + pkgconf_license_evaluate_str(client, &licenses, "\\", 0); + TEST_ASSERT_EQ(license_count(&licenses), 0); + + pkgconf_license_free(&licenses); + pkgconf_client_free(client); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_license_insert_and_free); + TEST_RUN(basename, test_license_free_empty); + + TEST_RUN(basename, test_license_evaluate_single); + TEST_RUN(basename, test_license_evaluate_or); + TEST_RUN(basename, test_license_evaluate_and); + TEST_RUN(basename, test_license_evaluate_multiple_keys_implicit_and); + TEST_RUN(basename, test_license_evaluate_brackets); + TEST_RUN(basename, test_license_evaluate_empty); + TEST_RUN(basename, test_license_evaluate_sanitizes); + TEST_RUN(basename, test_license_evaluate_long_sanitized_token); + TEST_RUN(basename, test_license_evaluate_unterminated_quote); + + TEST_RUN(basename, test_license_render_empty); + TEST_RUN(basename, test_license_render_single); + + TEST_RUN(basename, test_license_copy_list); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-path-utils.c b/tests/api/test-path-utils.c new file mode 100644 index 000000000000..519cadb6f639 --- /dev/null +++ b/tests/api/test-path-utils.c @@ -0,0 +1,119 @@ +/* + * test-path-utils.c + * Tests for libpkgconf internal path utility functions. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2025-2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include <libpkgconf/path.h> +#include "test-api.h" + +static void +test_path_find_basename(void) +{ + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("/usr/lib/pkgconfig/foo.pc"), "foo.pc"); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("/usr/lib/pkgconfig"), "pkgconfig"); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("foo.pc"), "foo.pc"); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("/foo.pc"), "foo.pc"); + TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename("/")); + TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename("")); + TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename("/usr/")); + TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename("usr/")); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("///usr/lib///pkgconfig///foo.pc"), "foo.pc"); +#ifdef _WIN32 + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:\\lib\\pkgconfig\\foo.pc"), "foo.pc"); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:/lib/pkgconfig/foo.pc"), "foo.pc"); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:/lib\\pkgconfig/foo.pc"), "foo.pc"); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:\\lib/pkgconfig\\foo.pc"), "foo.pc"); +#endif +} + +static void +test_path_trim_basename(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append(&buf, "/usr/lib/pkgconfig/foo.pc"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib/pkgconfig"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), ""); + TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), ""); + + pkgconf_buffer_reset(&buf); + pkgconf_buffer_append(&buf, "foo.pc"); + TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foo.pc"); + + pkgconf_buffer_finalize(&buf); +} + +static void +test_determine_prefix_logic(void) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + // Normal case + pkgconf_buffer_append(&buf, "/opt/foo/lib/pkgconfig/bar.pc"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename(pkgconf_buffer_str(&buf)), "pkgconfig"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/opt/foo"); + + // Short path: /pkgconfig/foo.pc + pkgconf_buffer_reset(&buf); + pkgconf_buffer_append(&buf, "/pkgconfig/foo.pc"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename(pkgconf_buffer_str(&buf)), "pkgconfig"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); // trims pkgconfig, returns true because of / + TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf)); // fails to trim further + + // Another short path: lib/pkgconfig/foo.pc + pkgconf_buffer_reset(&buf); + pkgconf_buffer_append(&buf, "lib/pkgconfig/foo.pc"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename(pkgconf_buffer_str(&buf)), "pkgconfig"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf)); + + // Trailing slash + pkgconf_buffer_reset(&buf); + pkgconf_buffer_append(&buf, "/usr/lib/pkgconfig/"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib/pkgconfig"); + TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); + TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib"); + + pkgconf_buffer_finalize(&buf); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_path_find_basename); + TEST_RUN(basename, test_path_trim_basename); + TEST_RUN(basename, test_determine_prefix_logic); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-tuple.c b/tests/api/test-tuple.c new file mode 100644 index 000000000000..b648abffe3f3 --- /dev/null +++ b/tests/api/test-tuple.c @@ -0,0 +1,258 @@ +/* + * test-tuple.c + * Tests for the public libpkgconf tuple API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "test-api.h" + +static void +test_tuple_add_and_find(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER; + + /* parse=false means store the value verbatim, no bytecode compile. + * That's the right mode for storing already-evaluated values. */ + pkgconf_tuple_t *t = pkgconf_tuple_add(client, &tuples, "prefix", "/opt/foo", false, 0); + TEST_ASSERT_NONNULL(t); + + const char *found = pkgconf_tuple_find(client, &tuples, "prefix"); + TEST_ASSERT_NONNULL(found); + TEST_ASSERT_STRCMP_EQ(found, "/opt/foo"); + + pkgconf_tuple_free(&tuples); + pkgconf_client_free(client); +} + +static void +test_tuple_find_absent(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER; + + const char *found = pkgconf_tuple_find(client, &tuples, "nonexistent"); + TEST_ASSERT_EMPTY_STRING(found); + + pkgconf_tuple_free(&tuples); + pkgconf_client_free(client); +} + +static void +test_tuple_add_multiple(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER; + + pkgconf_tuple_add(client, &tuples, "prefix", "/usr", false, 0); + pkgconf_tuple_add(client, &tuples, "exec_prefix", "/usr/local", false, 0); + pkgconf_tuple_add(client, &tuples, "libdir", "/usr/lib", false, 0); + + TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "prefix"), "/usr"); + TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "exec_prefix"), "/usr/local"); + TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "libdir"), "/usr/lib"); + TEST_ASSERT_EMPTY_STRING(pkgconf_tuple_find(client, &tuples, "datadir")); + + pkgconf_tuple_free(&tuples); + pkgconf_client_free(client); +} + +static void +test_tuple_global_add_and_find(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_tuple_add_global(client, "PKG_TEST_KEY", "test_value"); + + const char *found = pkgconf_tuple_find_global(client, "PKG_TEST_KEY"); + TEST_ASSERT_NONNULL(found); + TEST_ASSERT_STRCMP_EQ(found, "test_value"); + + pkgconf_tuple_free_global(client); + pkgconf_client_free(client); +} + +static void +test_tuple_global_find_absent(void) +{ + pkgconf_client_t *client = test_client_new(); + + const char *found = pkgconf_tuple_find_global(client, "DOES_NOT_EXIST"); + TEST_ASSERT_EMPTY_STRING(found); + + pkgconf_client_free(client); +} + +static void +test_tuple_define_global_kv_form(void) +{ + pkgconf_client_t *client = test_client_new(); + + /* This is the exact path --define-variable=KEY=VALUE takes + * pkgconf_tuple_define_global parses the "key=value" form and inserts it as a global tuple. */ + pkgconf_tuple_define_global(client, "myvar=myvalue"); + + const char *found = pkgconf_tuple_find_global(client, "myvar"); + TEST_ASSERT_NONNULL(found); + TEST_ASSERT_STRCMP_EQ(found, "myvalue"); + + pkgconf_tuple_free_global(client); + pkgconf_client_free(client); +} + +static void +test_tuple_define_global_with_equals_in_value(void) +{ + pkgconf_client_t *client = test_client_new(); + + // Only the first '=' should split key from value; subsequent '=' characters belong to the value. + pkgconf_tuple_define_global(client, "CFLAGS=-DFOO=bar"); + + const char *found = pkgconf_tuple_find_global(client, "CFLAGS"); + TEST_ASSERT_NONNULL(found); + TEST_ASSERT_STRCMP_EQ(found, "-DFOO=bar"); + + pkgconf_tuple_free_global(client); + pkgconf_client_free(client); +} + +static void +test_tuple_global_multiple(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_tuple_define_global(client, "a=1"); + pkgconf_tuple_define_global(client, "b=2"); + pkgconf_tuple_define_global(client, "c=3"); + + TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "a"), "1"); + TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "b"), "2"); + TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "c"), "3"); + + pkgconf_tuple_free_global(client); + pkgconf_client_free(client); +} + +static void +test_tuple_global_free_resets(void) +{ + pkgconf_client_t *client = test_client_new(); + + pkgconf_tuple_define_global(client, "temp=value"); + TEST_ASSERT_NONNULL(pkgconf_tuple_find_global(client, "temp")); + + pkgconf_tuple_free_global(client); + TEST_ASSERT_EMPTY_STRING(pkgconf_tuple_find_global(client, "temp")); + + pkgconf_client_free(client); +} + +static void +test_tuple_free_empty(void) +{ + pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER; + + // Freeing an empty list should be a no-op. Mostly an ASan/leak check smoke test. + pkgconf_tuple_free(&tuples); +} + +static void +test_tuple_define_variable_end_to_end(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + pkgconf_tuple_define_global(client, "myprefix=/opt/custom"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "-I${myprefix}/include", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "-I/opt/custom/include"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_tuple_free_global(client); + pkgconf_client_free(client); +} + +static void +test_tuple_define_variable_overrides_local(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "prefix"); + TEST_ASSERT_NONNULL(v); + pkgconf_buffer_reset(&v->bcbuf); + pkgconf_bytecode_compile(&v->bcbuf, "/usr"); + pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf); + + // Simulate user passing --define-variable=prefix=/custom. + pkgconf_tuple_define_global(client, "prefix=/custom"); + + char *out = pkgconf_bytecode_eval_str(client, &vars, "${prefix}/lib", &saw_sysroot); + + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "/custom/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_tuple_free_global(client); + pkgconf_client_free(client); +} + +static void +test_tuple_escaped_quote(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER; + + // A double-quoted value containing an escaped double-quote + pkgconf_tuple_t *t = pkgconf_tuple_add(client, &tuples, "key", "\"a\\\"b\"", true, 0); + TEST_ASSERT_NONNULL(t); + + const char *found = pkgconf_tuple_find(client, &tuples, "key"); + TEST_ASSERT_NONNULL(found); + TEST_ASSERT_STRCMP_EQ(found, "a\"b"); + + pkgconf_tuple_free(&tuples); + pkgconf_client_free(client); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_tuple_add_and_find); + TEST_RUN(basename, test_tuple_find_absent); + TEST_RUN(basename, test_tuple_add_multiple); + TEST_RUN(basename, test_tuple_free_empty); + TEST_RUN(basename, test_tuple_global_add_and_find); + TEST_RUN(basename, test_tuple_global_find_absent); + TEST_RUN(basename, test_tuple_define_global_kv_form); + TEST_RUN(basename, test_tuple_define_global_with_equals_in_value); + TEST_RUN(basename, test_tuple_global_multiple); + TEST_RUN(basename, test_tuple_global_free_resets); + TEST_RUN(basename, test_tuple_define_variable_end_to_end); + TEST_RUN(basename, test_tuple_define_variable_overrides_local); + TEST_RUN(basename, test_tuple_escaped_quote); + + return EXIT_SUCCESS; +} diff --git a/tests/api/test-variable.c b/tests/api/test-variable.c new file mode 100644 index 000000000000..ec65df3ae5a8 --- /dev/null +++ b/tests/api/test-variable.c @@ -0,0 +1,247 @@ +/* + * test-variable.c + * Tests for the public libpkgconf variable API. + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "test-api.h" + +/* + * Install a value on a variable by compiling it as bytecode. + * Mirrors what the parser does when it reads a .pc field. + */ +static void +seed_variable(pkgconf_list_t *vars, const char *key, const char *value) +{ + pkgconf_variable_t *v = pkgconf_variable_get_or_create(vars, key); + TEST_ASSERT_NONNULL(v); + + pkgconf_buffer_reset(&v->bcbuf); + pkgconf_bytecode_compile(&v->bcbuf, value); + pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf); +} + +static void +test_variable_new_and_free(void) +{ + pkgconf_variable_t *v = pkgconf_variable_new("prefix"); + + TEST_ASSERT_NONNULL(v); + TEST_ASSERT_STRCMP_EQ(v->key, "prefix"); + + pkgconf_variable_free(v); +} + +static void +test_variable_get_or_create_creates(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "prefix"); + TEST_ASSERT_NONNULL(v); + TEST_ASSERT_STRCMP_EQ(v->key, "prefix"); + + pkgconf_variable_list_free(&vars); +} + +static void +test_variable_get_or_create_returns_existing(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_variable_t *first = pkgconf_variable_get_or_create(&vars, "libdir"); + TEST_ASSERT_NONNULL(first); + + // A second call with the same key should return the same object, not create a duplicate. + pkgconf_variable_t *second = pkgconf_variable_get_or_create(&vars, "libdir"); + TEST_ASSERT_NONNULL(second); + TEST_ASSERT_EQ(first, second); + + pkgconf_variable_list_free(&vars); +} + +static void +test_variable_find_present(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_variable_t *created = pkgconf_variable_get_or_create(&vars, "prefix"); + TEST_ASSERT_NONNULL(created); + + pkgconf_variable_t *found = pkgconf_variable_find(&vars, "prefix"); + TEST_ASSERT_NONNULL(found); + TEST_ASSERT_EQ(created, found); + + pkgconf_variable_list_free(&vars); +} + +static void +test_variable_find_absent(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_variable_t *found = pkgconf_variable_find(&vars, "nonexistent"); + TEST_ASSERT_NULL(found); + + pkgconf_variable_list_free(&vars); +} + +static void +test_variable_find_among_many(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_variable_get_or_create(&vars, "prefix"); + pkgconf_variable_get_or_create(&vars, "exec_prefix"); + pkgconf_variable_get_or_create(&vars, "libdir"); + pkgconf_variable_get_or_create(&vars, "includedir"); + + TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "prefix")); + TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "exec_prefix")); + TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "libdir")); + TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "includedir")); + TEST_ASSERT_NULL(pkgconf_variable_find(&vars, "notpresent")); + + pkgconf_variable_list_free(&vars); +} + +static void +test_variable_delete(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "prefix"); + TEST_ASSERT_NONNULL(v); + TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "prefix")); + + pkgconf_variable_delete(&vars, v); + TEST_ASSERT_NULL(pkgconf_variable_find(&vars, "prefix")); + + pkgconf_variable_list_free(&vars); +} + +static void +test_variable_eval_str_plain(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + seed_variable(&vars, "version", "1.2.3"); + + pkgconf_variable_t *v = pkgconf_variable_find(&vars, "version"); + TEST_ASSERT_NONNULL(v); + + char *out = pkgconf_variable_eval_str(client, &vars, v, &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "1.2.3"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_variable_eval_str_with_reference(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + // Standard pkg-config layout: libdir is derived from prefix. + seed_variable(&vars, "prefix", "/opt/foo"); + seed_variable(&vars, "libdir", "${prefix}/lib"); + + pkgconf_variable_t *libdir = pkgconf_variable_find(&vars, "libdir"); + TEST_ASSERT_NONNULL(libdir); + + char *out = pkgconf_variable_eval_str(client, &vars, libdir, &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "/opt/foo/lib"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_variable_eval_str_chained(void) +{ + pkgconf_client_t *client = test_client_new(); + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + bool saw_sysroot = false; + + seed_variable(&vars, "prefix", "/usr/local"); + seed_variable(&vars, "exec_prefix", "${prefix}"); + seed_variable(&vars, "includedir", "${exec_prefix}/include"); + + pkgconf_variable_t *includedir = pkgconf_variable_find(&vars, "includedir"); + TEST_ASSERT_NONNULL(includedir); + + char *out = pkgconf_variable_eval_str(client, &vars, includedir, &saw_sysroot); + TEST_ASSERT_NONNULL(out); + TEST_ASSERT_STRCMP_EQ(out, "/usr/local/include"); + + free(out); + pkgconf_variable_list_free(&vars); + pkgconf_client_free(client); +} + +static void +test_variable_list_free_handles_empty(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + // Freeing an empty list should be a no-op and not crash. Mostly an ASan/leak smoke test. + pkgconf_variable_list_free(&vars); +} + +static void +test_variable_list_free_handles_many(void) +{ + pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER; + + /* Add a mix of bare and value-carrying variables, then free the whole list at once. + * Mostly an ASan/leak smoke test. */ + pkgconf_variable_get_or_create(&vars, "a"); + seed_variable(&vars, "b", "/path/b"); + pkgconf_variable_get_or_create(&vars, "c"); + seed_variable(&vars, "d", "${b}/sub"); + + pkgconf_variable_list_free(&vars); +} + +int +main(int argc, char *argv[]) +{ + (void) argc; + const char *basename = pkgconf_path_find_basename(argv[0]); + + TEST_RUN(basename, test_variable_new_and_free); + TEST_RUN(basename, test_variable_get_or_create_creates); + TEST_RUN(basename, test_variable_get_or_create_returns_existing); + TEST_RUN(basename, test_variable_find_present); + TEST_RUN(basename, test_variable_find_absent); + TEST_RUN(basename, test_variable_find_among_many); + TEST_RUN(basename, test_variable_delete); + TEST_RUN(basename, test_variable_eval_str_plain); + TEST_RUN(basename, test_variable_eval_str_with_reference); + TEST_RUN(basename, test_variable_eval_str_chained); + TEST_RUN(basename, test_variable_list_free_handles_empty); + TEST_RUN(basename, test_variable_list_free_handles_many); + + return EXIT_SUCCESS; +} |
