Files
profanity/tests/unittests/test_common.c
Jabber Developer 72f4f186da merge: sync upstream profanity-im/profanity
Sync with upstream profanity-im/profanity.

Major upstream changes incorporated:

Memory management
  - Replace malloc+memset with g_new0 throughout codebase
  - Adopt auto_gchar / auto_gcharv / auto_gerror / auto_jid cleanup macros
  - Replace free() with g_free() for GAlloc'd memory

Editor rewrite
  - Remove pthread-based async editor; use GChildWatch callback API
  - New launch_editor(initial_content, callback, user_data) interface
  - Proper signal handling (SIGINT, SIGTSTP, SIGPIPE reset in child)
  - ui_suspend()/ui_resume() integration for TTY management

OMEMO improvements
  - Dual backend support: libsignal-protocol-c and libomemo-c
  - Proper pre-key removal after use (XEP-0384 compliance)
  - Automatic pre-key regeneration when store drops below threshold
  - New functions: omemo_is_device_active(), omemo_is_jid_trusted()
  - omemo_get_jid_untrusted_fingerprints() for better error messages
  - Fingerprint notifications on new device identity discovery
  - Deterministic pre-key ID generation tracking max_pre_key_id
  - omemo_trust_changed() UI updates on trust state changes

JID validation
  - RFC 6122-compliant validation in jid_is_valid()
  - Character-level checks (RFC 6122 forbidden chars: & ' / : < > @)
  - Length limits: 1023 per component, 3071 total
  - New jid_is_valid_user_jid() for user vs. service JID distinction

Database
  - Schema migration v3: UNIQUE constraint on archive_id for deduplication
  - Triggers for corrected message tracking (replaces_db_id / replaced_by_db_id)
  - db_history_result_t return type, _truncate_datetime_suffix()

UI / console
  - win_warn_needed() / win_warn_sent() warning deduplication hash table
  - PAD_MIN_HEIGHT dynamic pad sizing with PAD_THRESHOLD auto-cleanup
  - Spellcheck integration in input field with Unicode word detection
  - cons_spellcheck_setting() for /settings ui output
  - /[command]? shortcut for command help

Account config
  - Account name sanitization for GKeyFile special chars ([ ] = # \n \r)
  - Replace popen() with g_spawn_sync() for eval_password
  - TLS policy: add "direct" option alongside legacy

Connection
  - Port validation with g_assert (0–65535)
  - SHA-256 certificate fingerprint support (XMPP_CERT_PUBKEY_FINGERPRINT_SHA256)
  - "direct" TLS policy alias for legacy SSL

Common utilities
  - str_xml_sanitize() for XML 1.0 illegal character removal
  - string_matches_one_of() with formatted error messages
  - valid_tls_policy_option() helper
  - prof_date_time_format_iso8601() utility
  - Improved strip_arg_quotes() with backslash unescaping
  - prof_occurrences() uses g_slist_prepend + reverse for performance

PGP / OX
  - Proper GPGME resource cleanup with goto-cleanup pattern
  - g_string_free(xmppuri) leak fix in _ox_key_lookup

CSV export
  - Use GString + g_file_set_contents instead of raw write() syscalls

Tests
  - Restructured into subdirectories: command/, config/, xmpp/, ui/, omemo/, otr/, pgp/
  - New test_cmd_ac.c for autocompleter unit tests
  - Updated stubs for new UI suspend/resume functions

License headers
  - Migrate to SPDX-3.0 identifiers (GPL-3.0-or-later WITH OpenSSL-exception)

────────────────────────────────────────────────────
cproof-specific preservations:

  - XEP-0308 LMC: replace_id ?: id logic in message/stanza/omemo
  - Force encryption: cmd_force_encryption, test_forced_encryption
  - CWE-134: format string protection (cons_show("%s", ...))
  - y_start_pos-based paging in window.c
  - db_history_result_t return type, _truncate_datetime_suffix()

Merge-time fixes:

  - common.c: format-security (-Werror) — cons_show(errmsg) → cons_show("%s", errmsg)
  - database.c: null-deref guard — !msg->timestamp → msg && !msg->timestamp
  - console.c: implicit size_t → int cast — (int)(maxlen + 1)
  - tlscerts.c: %d for size_t — %zu

Build system:
  - Kept autotools (Makefile.am, configure.ac); upstream uses Meson
  - Restored deleted files: bootstrap.sh, autogen.sh, ax_valgrind_check.m4, configure-debug
  - Updated Makefile.am test paths for subdirectory structure
  - Added test_cmd_ac, test_forced_encryption to test sources

Functional tests:
  - Use cproof version; upstream requires stbbr_for_xmlns from updated stabber
  - Not yet available in devs/stabber fork

Closes #64

Merge author: jabber.developer2
Commits authors:
 Michael Vetter <jubalh@iodoru.org>
& Steffen Jaeckel <s@jaeckel.eu>
2026-05-26 17:48:14 +00:00

1387 lines
43 KiB
C

/*
* test_common.c
*
* Copyright (C) 2015 - 2017 James Booth <boothj5@gmail.com>
* Copyright (C) 2015 - 2026 Michael Vetter <jubalh@iodoru.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "xmpp/resource.h"
#include "common.h"
#include "prof_cmocka.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include "tests/unittests/ui/stub_ui.h" // Include for mocking cons_show
void
str_replace__returns__one_substr(void** state)
{
char* string = "it is a string";
char* sub = "is";
char* new = "was";
char* result = str_replace(string, sub, new);
assert_string_equal("it was a string", result);
free(result);
}
void
str_replace__returns__one_substr_beginning(void** state)
{
char* string = "it is a string";
char* sub = "it";
char* new = "that";
char* result = str_replace(string, sub, new);
assert_string_equal("that is a string", result);
free(result);
}
void
str_replace__returns__one_substr_end(void** state)
{
char* string = "it is a string";
char* sub = "string";
char* new = "thing";
char* result = str_replace(string, sub, new);
assert_string_equal("it is a thing", result);
free(result);
}
void
str_replace__returns__two_substr(void** state)
{
char* string = "it is a is string";
char* sub = "is";
char* new = "was";
char* result = str_replace(string, sub, new);
assert_string_equal("it was a was string", result);
free(result);
}
void
str_replace__returns__char(void** state)
{
char* string = "some & a thing & something else";
char* sub = "&";
char* new = "&amp;";
char* result = str_replace(string, sub, new);
assert_string_equal("some &amp; a thing &amp; something else", result);
free(result);
}
void
str_replace__returns__original_when_none(void** state)
{
char* string = "its another string";
char* sub = "haha";
char* new = "replaced";
char* result = str_replace(string, sub, new);
assert_string_equal("its another string", result);
free(result);
}
void
str_replace__returns__replaced_when_match(void** state)
{
char* string = "hello";
char* sub = "hello";
char* new = "goodbye";
char* result = str_replace(string, sub, new);
assert_string_equal("goodbye", result);
free(result);
}
void
str_replace__returns__empty_when_string_empty(void** state)
{
char* string = "";
char* sub = "hello";
char* new = "goodbye";
char* result = str_replace(string, sub, new);
assert_string_equal("", result);
free(result);
}
void
str_replace__returns__null_when_string_null(void** state)
{
char* string = NULL;
char* sub = "hello";
char* new = "goodbye";
char* result = str_replace(string, sub, new);
assert_null(result);
}
void
str_replace__returns__original_when_sub_empty(void** state)
{
char* string = "hello";
char* sub = "";
char* new = "goodbye";
char* result = str_replace(string, sub, new);
assert_string_equal("hello", result);
free(result);
}
void
str_replace__returns__original_when_sub_null(void** state)
{
char* string = "hello";
char* sub = NULL;
char* new = "goodbye";
char* result = str_replace(string, sub, new);
assert_string_equal("hello", result);
free(result);
}
void
str_replace__returns__empty_when_new_empty(void** state)
{
char* string = "hello";
char* sub = "hello";
char* new = "";
char* result = str_replace(string, sub, new);
assert_string_equal("", result);
free(result);
}
void
str_replace__returns__original_when_new_null(void** state)
{
char* string = "hello";
char* sub = "hello";
char* new = NULL;
char* result = str_replace(string, sub, new);
assert_string_equal("hello", result);
free(result);
}
void
valid_resource_presence_string__is__true_for_online(void** state)
{
assert_true(valid_resource_presence_string("online"));
}
void
valid_resource_presence_string__is__true_for_chat(void** state)
{
assert_true(valid_resource_presence_string("chat"));
}
void
valid_resource_presence_string__is__true_for_away(void** state)
{
assert_true(valid_resource_presence_string("away"));
}
void
valid_resource_presence_string__is__true_for_xa(void** state)
{
assert_true(valid_resource_presence_string("xa"));
}
void
valid_resource_presence_string__is__true_for_dnd(void** state)
{
assert_true(valid_resource_presence_string("dnd"));
}
void
valid_resource_presence_string__is__false_for_available(void** state)
{
assert_false(valid_resource_presence_string("available"));
}
void
valid_resource_presence_string__is__false_for_unavailable(void** state)
{
assert_false(valid_resource_presence_string("unavailable"));
}
void
valid_resource_presence_string__is__false_for_blah(void** state)
{
assert_false(valid_resource_presence_string("blah"));
}
void
utf8_display_len__returns__0_for_null(void** state)
{
int result = utf8_display_len(NULL);
assert_int_equal(0, result);
}
void
utf8_display_len__returns__1_for_non_wide(void** state)
{
int result = utf8_display_len("1");
assert_int_equal(1, result);
}
void
utf8_display_len__returns__2_for_wide(void** state)
{
int result = utf8_display_len("");
assert_int_equal(2, result);
}
void
utf8_display_len__returns__correct_for_non_wide(void** state)
{
int result = utf8_display_len("123456789abcdef");
assert_int_equal(15, result);
}
void
utf8_display_len__returns__correct_for_wide(void** state)
{
int result = utf8_display_len("12三四56");
assert_int_equal(8, result);
}
void
utf8_display_len__returns__correct_for_all_wide(void** state)
{
int result = utf8_display_len("ひらがな");
assert_int_equal(8, result);
}
void
strip_arg_quotes__returns__original_when_no_quotes(void** state)
{
char* input = "/cmd test string";
char* result = strip_arg_quotes(input);
assert_string_equal("/cmd test string", result);
free(result);
}
void
strip_arg_quotes__returns__stripped_first(void** state)
{
char* input = "/cmd \"test string";
char* result = strip_arg_quotes(input);
assert_string_equal("/cmd test string", result);
free(result);
}
void
strip_arg_quotes__returns__stripped_last(void** state)
{
char* input = "/cmd test string\"";
char* result = strip_arg_quotes(input);
assert_string_equal("/cmd test string", result);
free(result);
}
void
strip_arg_quotes__returns__stripped_both(void** state)
{
char* input = "/cmd \"test string\"";
char* result = strip_arg_quotes(input);
assert_string_equal("/cmd test string", result);
free(result);
}
void
strip_arg_quotes__unescapes(void** state)
{
char* input = "\"Thor \\\"The Thunderer\\\" Odinson\"";
char* result = strip_arg_quotes(input);
assert_string_equal("Thor \"The Thunderer\" Odinson", result);
free(result);
}
void
valid_tls_policy_option__is__correct_for_various_inputs(void** state)
{
// Valid inputs
assert_true(valid_tls_policy_option("force"));
assert_true(valid_tls_policy_option("allow"));
assert_true(valid_tls_policy_option("trust"));
assert_true(valid_tls_policy_option("disable"));
assert_true(valid_tls_policy_option("legacy"));
assert_true(valid_tls_policy_option("direct"));
// Invalid inputs
// Not an option
expect_any_cons_show(); // For "Invalid TLS policy: 'profanity'"
expect_any_cons_show(); // For "TLS policy must be one of: 'force', 'allow', 'trust', 'disable', 'legacy', or 'direct'."
assert_false(valid_tls_policy_option("profanity"));
// Empty
expect_any_cons_show(); // For "Invalid TLS policy: ''"
expect_any_cons_show(); // For "TLS policy must be one of: 'force', 'allow', 'trust', 'disable', 'legacy', or 'direct'."
assert_false(valid_tls_policy_option(""));
// NULL
assert_true(valid_tls_policy_option(NULL));
}
void
get_expanded_path__returns__expanded(void** state)
{
gchar* expanded_path;
// `file://` prefix
expanded_path = get_expanded_path("file:///tmp/test.txt");
assert_string_equal("/tmp/test.txt", expanded_path);
g_free(expanded_path);
// `~/"` prefix
setenv("HOME", "/home/test", 1);
expanded_path = get_expanded_path("~/folder/file.txt");
assert_string_equal("/home/test/folder/file.txt", expanded_path);
g_free(expanded_path);
unsetenv("HOME");
// regular path
expanded_path = get_expanded_path("/home/test/file.pdf");
assert_string_equal("/home/test/file.pdf", expanded_path);
g_free(expanded_path);
// empty path
expanded_path = get_expanded_path("");
assert_string_equal("", expanded_path);
g_free(expanded_path);
}
void
strtoi_range__returns__true_for_valid_input(void** state)
{
int value;
gchar* err_msg = NULL;
gboolean result;
result = strtoi_range("10", &value, 0, 20, &err_msg);
assert_true(result);
assert_int_equal(10, value);
assert_null(err_msg);
result = strtoi_range("0", &value, 0, 20, &err_msg);
assert_true(result);
assert_int_equal(0, value);
assert_null(err_msg);
result = strtoi_range("20", &value, 0, 20, &err_msg);
assert_true(result);
assert_int_equal(20, value);
assert_null(err_msg);
result = strtoi_range("-5", &value, -10, 0, &err_msg);
assert_true(result);
assert_int_equal(-5, value);
assert_null(err_msg);
}
void
strtoi_range__returns__false_for_out_of_range(void** state)
{
int value;
gchar* err_msg = NULL;
gboolean result;
// too low, negative
result = strtoi_range("-11", &value, -10, 0, &err_msg);
assert_false(result);
assert_string_equal("Value -11 out of range. Must be in -10..0.", err_msg);
g_free(err_msg);
err_msg = NULL;
// too low
result = strtoi_range("-1", &value, 0, 10, &err_msg);
assert_false(result);
assert_string_equal("Value -1 out of range. Must be in 0..10.", err_msg);
g_free(err_msg);
err_msg = NULL;
// too high
result = strtoi_range("11", &value, 0, 10, &err_msg);
assert_false(result);
assert_string_equal("Value 11 out of range. Must be in 0..10.", err_msg);
g_free(err_msg);
err_msg = NULL;
}
void
strtoi_range__returns__false_for_invalid_input(void** state)
{
int value;
gchar* err_msg = NULL;
gboolean result;
// not a number
result = strtoi_range("profanity", &value, 0, 10, &err_msg);
assert_false(result);
assert_string_equal("Could not convert \"profanity\" to a number.", err_msg);
g_free(err_msg);
err_msg = NULL;
// not a number, start with digits
result = strtoi_range("23kk", &value, 0, 10, &err_msg);
assert_false(result);
assert_string_equal("Could not convert \"23kk\" to a number.", err_msg);
g_free(err_msg);
err_msg = NULL;
}
void
strtoi_range__returns__false_for_null_empty_input(void** state)
{
int value;
gchar* err_msg = NULL;
gboolean result;
// NULL input string
result = strtoi_range(NULL, &value, 0, 10, &err_msg);
assert_false(result);
assert_string_equal("'str' input pointer can not be NULL", err_msg);
g_free(err_msg);
err_msg = NULL;
// Empty input string
result = strtoi_range("", &value, 0, 10, &err_msg);
assert_false(result);
assert_string_equal("Could not convert \"\" to a number.", err_msg);
g_free(err_msg);
err_msg = NULL;
}
void
strtoi_range__returns__correct_values_when_err_msg_null(void** state)
{
int value;
gboolean result;
// valid conversion, err_msg is NULL
result = strtoi_range("5", &value, 0, 10, NULL);
assert_true(result);
assert_int_equal(5, value);
// invalid conversion, err_msg is NULL
result = strtoi_range("profanity", &value, 0, 10, NULL);
assert_false(result);
}
void
strtoi_range__returns__false_for_integer_overflow_underflow(void** state)
{
int value;
gchar* err_msg = NULL;
gboolean result;
char buf[64];
// INT_MAX + 1 overflows
snprintf(buf, sizeof(buf), "%lld", (long long)INT_MAX + 1);
result = strtoi_range(buf, &value, INT_MIN, INT_MAX, &err_msg);
assert_false(result);
g_free(err_msg);
err_msg = NULL;
// INT_MIN - 1 underflows
snprintf(buf, sizeof(buf), "%lld", (long long)INT_MIN - 1);
result = strtoi_range(buf, &value, INT_MIN, INT_MAX, &err_msg);
assert_false(result);
g_free(err_msg);
err_msg = NULL;
// Extreme overflow, larger than `long`
result = strtoi_range("9999999999999999999999999999999999", &value, INT_MIN, INT_MAX, &err_msg);
assert_false(result);
g_free(err_msg);
err_msg = NULL;
}
void
strtoi_range__returns_consistent_with_strtol_parsing(void** state)
{
int value;
gchar* err_msg = NULL;
gboolean result;
// Hexadecimal: "0x10" -> 16
result = strtoi_range("0x10", &value, 0, 100, &err_msg);
assert_true(result);
assert_int_equal(16, value);
g_free(err_msg);
err_msg = NULL;
// Octal: "010" -> 8
result = strtoi_range("010", &value, 0, 100, &err_msg);
assert_true(result);
assert_int_equal(8, value);
g_free(err_msg);
err_msg = NULL;
// Floating point "0.8" should fail: '.' is not a valid integer char
result = strtoi_range("0.8", &value, -10, 10, &err_msg);
assert_false(result);
g_free(err_msg);
err_msg = NULL;
}
void
string_to_verbosity__returns__correct_values(void** state)
{
int verbosity;
gchar* err_msg = NULL;
gboolean result;
// Valid input string (0)
result = string_to_verbosity("0", &verbosity, &err_msg);
assert_true(result);
assert_int_equal(0, verbosity);
assert_null(err_msg);
g_free(err_msg);
err_msg = NULL; // Clear for next test
// Valid input string (1)
result = string_to_verbosity("1", &verbosity, &err_msg);
assert_true(result);
assert_int_equal(1, verbosity);
assert_null(err_msg);
g_free(err_msg);
err_msg = NULL;
// Valid input string (3)
result = string_to_verbosity("3", &verbosity, &err_msg);
assert_true(result);
assert_int_equal(3, verbosity);
assert_null(err_msg);
g_free(err_msg);
err_msg = NULL;
// Invalid input string (not a number)
result = string_to_verbosity("profanity", &verbosity, &err_msg);
assert_false(result);
assert_string_equal("Could not convert \"profanity\" to a number.", err_msg);
g_free(err_msg);
err_msg = NULL;
// Valid input string, out of range (too low)
result = string_to_verbosity("-1", &verbosity, &err_msg);
assert_false(result);
assert_string_equal("Value -1 out of range. Must be in 0..3.", err_msg);
g_free(err_msg);
err_msg = NULL;
// Valid input string, out of range (too high)
result = string_to_verbosity("4", &verbosity, &err_msg);
assert_false(result);
assert_string_equal("Value 4 out of range. Must be in 0..3.", err_msg);
g_free(err_msg);
err_msg = NULL;
// NULL input string
result = string_to_verbosity(NULL, &verbosity, &err_msg);
assert_false(result);
assert_string_equal("'str' input pointer can not be NULL", err_msg);
g_free(err_msg);
err_msg = NULL;
// Empty input string
result = string_to_verbosity("", &verbosity, &err_msg);
assert_false(result);
assert_string_equal("Could not convert \"\" to a number.", err_msg);
g_free(err_msg);
err_msg = NULL;
// err_msg is NULL
result = string_to_verbosity("abc", &verbosity, NULL);
assert_false(result);
}
typedef struct
{
char* template;
char* url;
char* filename;
char* argv;
} format_call_external_argv_t;
void
format_call_external_argv__tests__table_driven(void** state)
{
enum table { num_tests = 4 };
format_call_external_argv_t tests[num_tests] = {
(format_call_external_argv_t){
.template = "/bin/echo %u %p",
.url = "https://example.org",
.filename = "image.jpeg",
.argv = "/bin/echo https://example.org image.jpeg",
},
(format_call_external_argv_t){
.template = "/bin/echo %p %u",
.url = "https://example.org",
.filename = "image.jpeg",
.argv = "/bin/echo image.jpeg https://example.org",
},
(format_call_external_argv_t){
.template = "/bin/echo %p",
.url = "https://example.org",
.filename = "image.jpeg",
.argv = "/bin/echo image.jpeg",
},
(format_call_external_argv_t){
.template = "/bin/echo %u",
.url = "https://example.org",
.filename = "image.jpeg",
.argv = "/bin/echo https://example.org",
},
};
gchar** got_argv = NULL;
gchar* got_argv_str = NULL;
for (int i = 0; i < num_tests; i++) {
got_argv = format_call_external_argv(
tests[i].template,
tests[i].url,
tests[i].filename);
got_argv_str = g_strjoinv(" ", got_argv);
assert_string_equal(got_argv_str, tests[i].argv);
g_strfreev(got_argv);
g_free(got_argv_str);
}
}
typedef struct
{
char* url;
char* path;
char* target;
char* basename;
} unique_filename_from_url_t;
void
unique_filename_from_url__tests__table_driven(void** state)
{
enum table { num_tests = 15 };
auto_gchar gchar* pwd = g_get_current_dir();
unique_filename_from_url_t tests[num_tests] = {
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg",
.path = "./.",
.target = pwd,
.basename = "image.jpeg",
},
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg",
.path = NULL,
.target = pwd,
.basename = "image.jpeg",
},
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg#somefragment",
.path = "./",
.target = pwd,
.basename = "image.jpeg",
},
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg?query=param",
.path = "./",
.target = pwd,
.basename = "image.jpeg",
},
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg?query=param&another=one",
.path = "./",
.target = pwd,
.basename = "image.jpeg",
},
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg?query=param&another=one",
.path = "/tmp/",
.target = "/tmp/",
.basename = "image.jpeg",
},
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg?query=param&another=one",
.path = "/tmp/hopefully/this/file/does/not/exist",
.target = "/tmp/hopefully/this/file/does/not/",
.basename = "exist",
},
(unique_filename_from_url_t){
.url = "https://host.test/image.jpeg?query=param&another=one",
.path = "/tmp/hopefully/this/file/does/not/exist/",
.target = "/tmp/hopefully/this/file/does/not/exist/",
.basename = "image.jpeg",
},
(unique_filename_from_url_t){
.url = "https://host.test/images/",
.path = "./",
.target = pwd,
.basename = "images",
},
(unique_filename_from_url_t){
.url = "https://host.test/images/../../file",
.path = "./",
.target = pwd,
.basename = "file",
},
(unique_filename_from_url_t){
.url = "https://host.test/images/../../file/..",
.path = "./",
.target = pwd,
.basename = "index",
},
(unique_filename_from_url_t){
.url = "https://host.test/images/..//",
.path = "./",
.target = pwd,
.basename = "index",
},
(unique_filename_from_url_t){
.url = "https://host.test/",
.path = "./",
.target = pwd,
.basename = "index",
},
(unique_filename_from_url_t){
.url = "https://host.test",
.path = "./",
.target = pwd,
.basename = "index",
},
(unique_filename_from_url_t){
.url = "aesgcm://host.test",
.path = "./",
.target = pwd,
.basename = "index",
},
};
char* got_filename = NULL;
char* exp_filename = NULL;
for (int i = 0; i < num_tests; i++) {
got_filename = unique_filename_from_url(tests[i].url, tests[i].path);
exp_filename = g_build_filename(tests[i].target, tests[i].basename, NULL);
assert_string_equal(got_filename, exp_filename);
g_free(got_filename);
g_free(exp_filename);
}
}
gboolean
_lists_equal(GSList* a, GSList* b)
{
if (g_slist_length(a) != g_slist_length(b)) {
return FALSE;
}
GSList* curra = a;
GSList* currb = b;
while (curra && currb) {
int aval = GPOINTER_TO_INT(curra->data);
int bval = GPOINTER_TO_INT(currb->data);
if (aval != bval) {
return FALSE;
}
curra = g_slist_next(curra);
currb = g_slist_next(currb);
}
return TRUE;
}
void
prof_occurrences__tests__large_message(void** state)
{
GSList* actual = NULL;
GSList* expected = NULL;
/* use this with the old implementation to create a segfault
* const size_t haystack_sz = 1024 * 1024;
*/
const size_t haystack_sz = 1024;
size_t haystack_cur = 0;
auto_char char* haystack = malloc(haystack_sz);
const char needle[] = "needle ";
while (haystack_sz - haystack_cur > sizeof(needle)) {
memcpy(&haystack[haystack_cur], needle, sizeof(needle));
expected = g_slist_append(expected, GINT_TO_POINTER(haystack_cur));
haystack_cur += sizeof(needle) - 1;
}
assert_true(_lists_equal(prof_occurrences("needle", haystack, 0, FALSE, &actual), expected));
g_slist_free(actual);
g_slist_free(expected);
}
void
prof_occurrences__tests__partial(void** state)
{
GSList* actual = NULL;
GSList* expected = NULL;
assert_true(_lists_equal(prof_occurrences(NULL, NULL, 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences(NULL, "some string", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", NULL, 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences(NULL, NULL, 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "Boothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("Boothj5", "boothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(0));
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5hello", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5 hello", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(5));
assert_true(_lists_equal(prof_occurrences("boothj5", "helloboothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "helloboothj5hello", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(6));
assert_true(_lists_equal(prof_occurrences("boothj5", "hello boothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "hello boothj5 hello", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(0));
expected = g_slist_append(expected, GINT_TO_POINTER(7));
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5boothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(0));
expected = g_slist_append(expected, GINT_TO_POINTER(12));
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5helloboothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(0));
expected = g_slist_append(expected, GINT_TO_POINTER(14));
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5 hello boothj5", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(2));
expected = g_slist_append(expected, GINT_TO_POINTER(16));
expected = g_slist_append(expected, GINT_TO_POINTER(29));
assert_true(_lists_equal(prof_occurrences("boothj5", "hiboothj5 hello boothj5there boothj5s", 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
}
void
get_mentions__tests__various(void** state)
{
GSList* actual = NULL;
GSList* expected = NULL;
// Basic match, case sensitive
expected = g_slist_append(expected, GINT_TO_POINTER(6));
actual = get_mentions(FALSE, TRUE, "hello boothj5", "boothj5");
assert_true(_lists_equal(actual, expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
// Case insensitive match
expected = g_slist_append(expected, GINT_TO_POINTER(6));
actual = get_mentions(FALSE, FALSE, "hello BOOTHJ5", "boothj5");
assert_true(_lists_equal(actual, expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
// Whole word match
expected = g_slist_append(expected, GINT_TO_POINTER(0));
actual = get_mentions(TRUE, TRUE, "boothj5 hello", "boothj5");
assert_true(_lists_equal(actual, expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
// Whole word no match (partial)
actual = get_mentions(TRUE, TRUE, "boothj5hello", "boothj5");
assert_true(_lists_equal(actual, expected)); // expected is NULL
g_slist_free(actual);
actual = NULL;
// Multiple matches
expected = g_slist_append(expected, GINT_TO_POINTER(0));
expected = g_slist_append(expected, GINT_TO_POINTER(14));
actual = get_mentions(FALSE, TRUE, "boothj5 hello boothj5", "boothj5");
assert_true(_lists_equal(actual, expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
// Nick with punctuation (whole word)
expected = g_slist_append(expected, GINT_TO_POINTER(0));
actual = get_mentions(TRUE, TRUE, "boothj5: hi", "boothj5");
assert_true(_lists_equal(actual, expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
// Nick surrounded by punctuation
expected = g_slist_append(expected, GINT_TO_POINTER(1));
actual = get_mentions(TRUE, TRUE, "(boothj5)", "boothj5");
assert_true(_lists_equal(actual, expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
// Empty message
actual = get_mentions(FALSE, TRUE, "", "boothj5");
assert_true(_lists_equal(actual, expected)); // expected is NULL
g_slist_free(actual);
actual = NULL;
// Empty nick
actual = get_mentions(FALSE, TRUE, "hello", "");
assert_true(_lists_equal(actual, expected)); // expected is NULL
g_slist_free(actual);
actual = NULL;
// UTF-8 characters
expected = g_slist_append(expected, GINT_TO_POINTER(0));
actual = get_mentions(TRUE, TRUE, "我能 hello", "我能");
assert_true(_lists_equal(actual, expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
}
void
release_is_new__tests__various(void** state)
{
// Higher major version
assert_true(release_is_new("0.16.0", "1.0.0"));
// Higher minor version
assert_true(release_is_new("0.16.0", "0.17.0"));
// Higher patch version
assert_true(release_is_new("0.16.0", "0.16.1"));
// Same version
assert_false(release_is_new("0.16.0", "0.16.0"));
// Lower major version
assert_false(release_is_new("0.16.0", "0.15.9"));
// Lower minor version
assert_false(release_is_new("0.16.0", "0.15.0"));
// Lower patch version
assert_false(release_is_new("0.16.1", "0.16.0"));
// Higher version but with different current versions
assert_true(release_is_new("1.2.3", "1.2.4"));
assert_true(release_is_new("1.2.3", "1.3.0"));
assert_true(release_is_new("1.2.3", "2.0.0"));
// Malformed version strings
assert_false(release_is_new("0.16.0", "0.16")); // Missing patch in found
assert_false(release_is_new("0.16", "0.16.0")); // Missing patch in curr
assert_false(release_is_new("0.16.0", "0.16.0.1")); // Extra part
assert_false(release_is_new("0.16.0", "abc.def.ghi"));
assert_false(release_is_new("0.16.0", ""));
assert_false(release_is_new(NULL, "1.0.0"));
assert_false(release_is_new("1.0.0", NULL));
}
void
prof_occurrences__tests__whole(void** state)
{
GSList* actual = NULL;
GSList* expected = NULL;
assert_true(_lists_equal(prof_occurrences(NULL, NULL, 0, FALSE, &actual), expected));
g_slist_free(actual);
actual = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(0));
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5 hi", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5: hi", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5, hi", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(0));
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "我能吞下玻璃而", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "我能吞下玻璃而 hi", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "我能吞下玻璃而: hi", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "我能吞下玻璃而, hi", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(6));
assert_true(_lists_equal(prof_occurrences("boothj5", "hello boothj5", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "hello boothj5 there", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "heyy @boothj5, there", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(6));
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hello 我能吞下玻璃而", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hello 我能吞下玻璃而 there", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "heyy @我能吞下玻璃而, there", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(6));
expected = g_slist_append(expected, GINT_TO_POINTER(26));
assert_true(_lists_equal(prof_occurrences("boothj5", "hello boothj5 some more a boothj5 stuff", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "hello boothj5 there ands #boothj5", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "heyy @boothj5, there hows boothj5?", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(6));
expected = g_slist_append(expected, GINT_TO_POINTER(26));
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hello 我能吞下玻璃而 some more a 我能吞下玻璃而 stuff", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hello 我能吞下玻璃而 there ands #我能吞下玻璃而", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "heyy @我能吞下玻璃而, there hows 我能吞下玻璃而?", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(6));
assert_true(_lists_equal(prof_occurrences("p", "ppppp p", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(0));
assert_true(_lists_equal(prof_occurrences("p", "p ppppp", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = g_slist_append(expected, GINT_TO_POINTER(4));
assert_true(_lists_equal(prof_occurrences("p", "ppp p ppp", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5hello", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "heyboothj5", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "heyboothj5hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "hey boothj5hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "hey @boothj5hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "heyboothj5 hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "heyboothj5, hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5boothj5", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("boothj5", "boothj5fillboothj5", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "dont know", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "kick", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "kick kick", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "kick kickk", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "kic", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "ick", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "kk", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("k", "kkkkkkk", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
expected = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "我能吞下玻璃而hello", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hey我能吞下玻璃而", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hey我能吞下玻璃而hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hey 我能吞下玻璃而hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hey @我能吞下玻璃而hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hey我能吞下玻璃而 hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "hey我能吞下玻璃而, hithere", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "我能吞下玻璃而我能吞下玻璃而", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
assert_true(_lists_equal(prof_occurrences("我能吞下玻璃而", "我能吞下玻璃而fill我能吞下玻璃而", 0, TRUE, &actual), expected));
g_slist_free(actual);
actual = NULL;
g_slist_free(expected);
expected = NULL;
}
void
string_matches_one_of__tests__edge_cases(void** state)
{
// is is NULL, is_can_be_null is TRUE -> should return TRUE
assert_true(string_matches_one_of(NULL, NULL, TRUE, "option1", "option2", NULL));
// is is NULL, is_can_be_null is FALSE -> should return FALSE
assert_false(string_matches_one_of(NULL, NULL, FALSE, "option1", "option2", NULL));
// is matches one of the options
assert_true(string_matches_one_of("Test", "option1", FALSE, "option1", "option2", NULL));
assert_true(string_matches_one_of("Test", "option2", FALSE, "option1", "option2", NULL));
// is does not match any of the options
expect_any_cons_show(); // For "Invalid Test: 'option3'"
expect_any_cons_show(); // For "Test must be one of: 'option1', or 'option2'."
assert_false(string_matches_one_of("Test", "option3", FALSE, "option1", "option2", NULL));
// what is NULL (no error message printed)
assert_false(string_matches_one_of(NULL, "option3", FALSE, "option1", "option2", NULL));
// Empty options list (first is NULL)
expect_any_cons_show(); // For "Invalid Test: 'option1'"
expect_any_cons_show(); // For "Test must be one of: ." (empty options list error message)
assert_false(string_matches_one_of("Test", "option1", FALSE, NULL, NULL));
assert_false(string_matches_one_of(NULL, NULL, FALSE, NULL, NULL));
assert_true(string_matches_one_of(NULL, NULL, TRUE, NULL, NULL));
// Single option, matches
assert_true(string_matches_one_of("Test", "single", FALSE, "single", NULL));
// Single option, no match
expect_any_cons_show(); // For "Invalid Test: 'nomatch'"
expect_any_cons_show(); // For "Test must be one of: 'single'."
assert_false(string_matches_one_of("Test", "nomatch", FALSE, "single", NULL));
// Multiple options, first matches
assert_true(string_matches_one_of("Test", "first", FALSE, "first", "second", "third", NULL));
// Multiple options, middle matches
assert_true(string_matches_one_of("Test", "second", FALSE, "first", "second", "third", NULL));
// Multiple options, last matches
assert_true(string_matches_one_of("Test", "third", FALSE, "first", "second", "third", NULL));
// Multiple options, no match
expect_any_cons_show(); // For "Invalid Test: 'none'"
expect_any_cons_show(); // For "Test must be one of: 'first', 'second', or 'third'."
assert_false(string_matches_one_of("Test", "none", FALSE, "first", "second", "third", NULL));
// is is an empty string, options are not
expect_any_cons_show(); // For "Invalid Test: ''"
expect_any_cons_show(); // For "Test must be one of: 'option1', or 'option2'."
assert_false(string_matches_one_of("Test", "", FALSE, "option1", "option2", NULL));
// is is an empty string, one of the options is an empty string
assert_true(string_matches_one_of("Test", "", FALSE, "option1", "", "option2", NULL));
}
void
str_xml_sanitize__strips_illegal_characters(void** state)
{
// Test NULL input
assert_null(str_xml_sanitize(NULL));
// Test empty string
gchar* res1 = str_xml_sanitize("");
assert_string_equal("", res1);
g_free(res1);
// Test string with no illegal characters
gchar* res2 = str_xml_sanitize("Hello World! \t\n\r");
assert_string_equal("Hello World! \t\n\r", res2);
g_free(res2);
// Test string with illegal characters (0x16 is ^V, 0x01 is ^A)
gchar* res3 = str_xml_sanitize("Hello\x16World\x01!");
assert_string_equal("HelloWorld!", res3);
g_free(res3);
// Test string with mixed legal and illegal control characters
gchar* res4 = str_xml_sanitize("\x09Legal\x0BIllegal\x0ALegal\x1FIllegal\x0DLegal");
assert_string_equal("\tLegalIllegal\nLegalIllegal\rLegal", res4);
g_free(res4);
// Test UTF-8 characters
gchar* res5 = str_xml_sanitize("UTF-8: üñîçøðé \x16 and more");
assert_string_equal("UTF-8: üñîçøðé and more", res5);
g_free(res5);
}