Compare commits
4 Commits
feat/no-db
...
fix/verify
| Author | SHA1 | Date | |
|---|---|---|---|
|
1aaa382d5e
|
|||
|
3f36c303c2
|
|||
|
0feacbc9da
|
|||
|
0722dc9e36
|
@@ -204,6 +204,13 @@ case "$ARCH" in
|
||||
""
|
||||
)
|
||||
source /etc/profile.d/debuginfod.sh 2>/dev/null || true
|
||||
|
||||
if grep -q 'ID=arch' /etc/os-release 2>/dev/null && [ -f /etc/makepkg.conf ]; then
|
||||
echo "--> [Parity Mode] Simulating Pikaur collision..."
|
||||
set -a
|
||||
source /etc/makepkg.conf
|
||||
set +a
|
||||
fi
|
||||
;;
|
||||
darwin*)
|
||||
# 4 configurations for parallel CI
|
||||
|
||||
@@ -396,12 +396,11 @@ AC_SUBST([FORKPTY_LIB])
|
||||
## Default parameters
|
||||
AM_CFLAGS="$AM_CFLAGS -Wall -Wextra -Wformat=2 -Wno-format-zero-length"
|
||||
AM_CFLAGS="$AM_CFLAGS -Wno-deprecated-declarations -Wno-unused-parameter -Wno-missing-field-initializers -Wno-sign-compare -Wno-cast-function-type"
|
||||
AM_CFLAGS="$AM_CFLAGS -Wnull-dereference -Wpointer-arith"
|
||||
AM_CFLAGS="$AM_CFLAGS -Wpointer-arith"
|
||||
AM_CFLAGS="$AM_CFLAGS -Wimplicit-function-declaration"
|
||||
AM_CFLAGS="$AM_CFLAGS -Wundef"
|
||||
AM_CFLAGS="$AM_CFLAGS -Wfloat-equal -Wredundant-decls"
|
||||
AM_CFLAGS="$AM_CFLAGS -fstack-protector-strong -fno-common"
|
||||
AM_CFLAGS="$AM_CFLAGS -D_FORTIFY_SOURCE=2"
|
||||
AM_CFLAGS="$AM_CFLAGS -std=gnu99 -ggdb3"
|
||||
|
||||
# GCC-specific warnings (not supported by clang) — test each one
|
||||
|
||||
@@ -310,11 +310,9 @@ _verify_contact_dir(const char* cdir_path, GSList** issues)
|
||||
// identify which contact the problem belongs to (every contact has its
|
||||
// own history.log, so the bare filename is ambiguous).
|
||||
auto_gchar gchar* dir_name = g_path_get_basename(cdir_path);
|
||||
char* dir_unescaped = str_replace(dir_name, "_at_", "@");
|
||||
auto_gchar gchar* basename_owned = g_strdup_printf("%s/history.log",
|
||||
dir_unescaped ? dir_unescaped : dir_name);
|
||||
free(dir_unescaped);
|
||||
const char* basename = basename_owned;
|
||||
auto_gcharv gchar** parts = g_strsplit(dir_name, "_at_", -1);
|
||||
auto_gchar gchar* contact_jid = g_strjoinv("@", parts);
|
||||
auto_gchar gchar* basename = g_strdup_printf("%s/history.log", contact_jid);
|
||||
|
||||
_check_permissions(filepath, basename, issues);
|
||||
|
||||
|
||||
@@ -510,10 +510,10 @@ _sqlite_verify_integrity(const gchar* const contact_barejid)
|
||||
|
||||
integrity_issue_t* issue = g_malloc0(sizeof(integrity_issue_t));
|
||||
issue->level = INTEGRITY_WARNING;
|
||||
issue->file = g_strdup("chatlog.db");
|
||||
issue->file = g_strdup_printf("%s↔%s/chatlog.db",
|
||||
from_a ? from_a : "?", to_a ? to_a : "?");
|
||||
issue->line = id_a;
|
||||
issue->message = g_strdup_printf("Timestamp out of order in %s↔%s: row %d (%s) > row %d (%s)",
|
||||
from_a ? from_a : "?", to_a ? to_a : "?",
|
||||
issue->message = g_strdup_printf("Timestamp out of order: row %d (%s) > row %d (%s)",
|
||||
id_a, ts_a ? ts_a : "NULL",
|
||||
id_b, ts_b ? ts_b : "NULL");
|
||||
issues = g_slist_append(issues, issue);
|
||||
@@ -537,10 +537,10 @@ _sqlite_verify_integrity(const gchar* const contact_barejid)
|
||||
|
||||
integrity_issue_t* issue = g_malloc0(sizeof(integrity_issue_t));
|
||||
issue->level = INTEGRITY_ERROR;
|
||||
issue->file = g_strdup("chatlog.db");
|
||||
issue->file = g_strdup_printf("%s↔%s/chatlog.db",
|
||||
from ? from : "?", to ? to : "?");
|
||||
issue->line = id;
|
||||
issue->message = g_strdup_printf("Broken LMC reference in %s↔%s: row %d references non-existent row %d",
|
||||
from ? from : "?", to ? to : "?",
|
||||
issue->message = g_strdup_printf("Broken LMC reference: row %d references non-existent row %d",
|
||||
id, replaces_id);
|
||||
issues = g_slist_append(issues, issue);
|
||||
}
|
||||
|
||||
@@ -70,9 +70,12 @@ const char*
|
||||
bench_volume_name(bench_volume_t v)
|
||||
{
|
||||
switch (v) {
|
||||
case BENCH_VOLUME_SMALL: return "small";
|
||||
case BENCH_VOLUME_MEDIUM: return "medium";
|
||||
case BENCH_VOLUME_MAX: return "max";
|
||||
case BENCH_VOLUME_SMALL:
|
||||
return "small";
|
||||
case BENCH_VOLUME_MEDIUM:
|
||||
return "medium";
|
||||
case BENCH_VOLUME_MAX:
|
||||
return "max";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ char* bench_fmt_bytes(uint64_t bytes);
|
||||
char* bench_fmt_ms(double ms);
|
||||
|
||||
// Resolve BENCH_VOLUME env: "small" / "medium" / "max" / unset.
|
||||
typedef enum
|
||||
{
|
||||
typedef enum {
|
||||
BENCH_VOLUME_SMALL, // 10k lines / 1 contact / 1 year
|
||||
BENCH_VOLUME_MEDIUM, // 500k lines / 1 contact / 5 years
|
||||
BENCH_VOLUME_MAX, // 5M lines / 1 contact / 10 years
|
||||
|
||||
@@ -105,26 +105,25 @@ db_path_for(const char* account_jid)
|
||||
// Schema (lifted from database_sqlite.c so the seeder can populate without
|
||||
// going through _sqlite_init's full machinery)
|
||||
|
||||
static const char* SCHEMA_DDL =
|
||||
"CREATE TABLE IF NOT EXISTS `ChatLogs` ("
|
||||
"`id` INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"`from_jid` TEXT NOT NULL, "
|
||||
"`to_jid` TEXT NOT NULL, "
|
||||
"`from_resource` TEXT, `to_resource` TEXT, "
|
||||
"`message` TEXT, `timestamp` TEXT, `type` TEXT, "
|
||||
"`stanza_id` TEXT, `archive_id` TEXT, "
|
||||
"`encryption` TEXT, `marked_read` INTEGER, "
|
||||
"`replace_id` TEXT, "
|
||||
"`replaces_db_id` INTEGER, `replaced_by_db_id` INTEGER);"
|
||||
"CREATE TABLE IF NOT EXISTS `DbVersion` ("
|
||||
"`dv_id` INTEGER PRIMARY KEY, `version` INTEGER UNIQUE);"
|
||||
"INSERT OR IGNORE INTO `DbVersion` (`version`) VALUES ('2');"
|
||||
"CREATE INDEX IF NOT EXISTS ChatLogs_timestamp_IDX ON `ChatLogs` (`timestamp`);"
|
||||
"CREATE INDEX IF NOT EXISTS ChatLogs_to_from_jid_IDX ON `ChatLogs` (`to_jid`, `from_jid`);"
|
||||
"CREATE TRIGGER IF NOT EXISTS update_corrected_message "
|
||||
"AFTER INSERT ON ChatLogs FOR EACH ROW WHEN NEW.replaces_db_id IS NOT NULL "
|
||||
"BEGIN UPDATE ChatLogs SET replaced_by_db_id = NEW.id "
|
||||
"WHERE id = NEW.replaces_db_id; END;";
|
||||
static const char* SCHEMA_DDL = "CREATE TABLE IF NOT EXISTS `ChatLogs` ("
|
||||
"`id` INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"`from_jid` TEXT NOT NULL, "
|
||||
"`to_jid` TEXT NOT NULL, "
|
||||
"`from_resource` TEXT, `to_resource` TEXT, "
|
||||
"`message` TEXT, `timestamp` TEXT, `type` TEXT, "
|
||||
"`stanza_id` TEXT, `archive_id` TEXT, "
|
||||
"`encryption` TEXT, `marked_read` INTEGER, "
|
||||
"`replace_id` TEXT, "
|
||||
"`replaces_db_id` INTEGER, `replaced_by_db_id` INTEGER);"
|
||||
"CREATE TABLE IF NOT EXISTS `DbVersion` ("
|
||||
"`dv_id` INTEGER PRIMARY KEY, `version` INTEGER UNIQUE);"
|
||||
"INSERT OR IGNORE INTO `DbVersion` (`version`) VALUES ('2');"
|
||||
"CREATE INDEX IF NOT EXISTS ChatLogs_timestamp_IDX ON `ChatLogs` (`timestamp`);"
|
||||
"CREATE INDEX IF NOT EXISTS ChatLogs_to_from_jid_IDX ON `ChatLogs` (`to_jid`, `from_jid`);"
|
||||
"CREATE TRIGGER IF NOT EXISTS update_corrected_message "
|
||||
"AFTER INSERT ON ChatLogs FOR EACH ROW WHEN NEW.replaces_db_id IS NOT NULL "
|
||||
"BEGIN UPDATE ChatLogs SET replaced_by_db_id = NEW.id "
|
||||
"WHERE id = NEW.replaces_db_id; END;";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Seed: open sqlite3 directly, build schema, batched-INSERT N rows.
|
||||
@@ -160,14 +159,15 @@ do_seed(const seed_opts_t* o)
|
||||
|
||||
if (sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "seed: BEGIN failed: %s\n", err);
|
||||
sqlite3_free(err); sqlite3_close(db); return 2;
|
||||
sqlite3_free(err);
|
||||
sqlite3_close(db);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const char* INSERT_SQL =
|
||||
"INSERT INTO ChatLogs ("
|
||||
"from_jid, to_jid, from_resource, to_resource, message, timestamp, type, "
|
||||
"stanza_id, archive_id, encryption, marked_read, replace_id"
|
||||
") VALUES (?, ?, ?, ?, ?, ?, 'chat', ?, ?, 'none', -1, ?);";
|
||||
const char* INSERT_SQL = "INSERT INTO ChatLogs ("
|
||||
"from_jid, to_jid, from_resource, to_resource, message, timestamp, type, "
|
||||
"stanza_id, archive_id, encryption, marked_read, replace_id"
|
||||
") VALUES (?, ?, ?, ?, ?, ?, 'chat', ?, ?, 'none', -1, ?);";
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
if (sqlite3_prepare_v2(db, INSERT_SQL, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
fprintf(stderr, "seed: prepare failed: %s\n", sqlite3_errmsg(db));
|
||||
@@ -180,7 +180,8 @@ do_seed(const seed_opts_t* o)
|
||||
int64_t span_secs = (int64_t)5 * 365 * 24 * 3600; // 5 years
|
||||
char* prev_sid_per_contact[1024] = { 0 };
|
||||
int n_contacts = o->contacts > 0 ? o->contacts : 1;
|
||||
if (n_contacts > 1024) n_contacts = 1024;
|
||||
if (n_contacts > 1024)
|
||||
n_contacts = 1024;
|
||||
|
||||
double t0 = bench_now_ms();
|
||||
|
||||
@@ -209,10 +210,14 @@ do_seed(const seed_opts_t* o)
|
||||
sqlite3_bind_text(stmt, 5, body, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 6, iso, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 7, sid, -1, SQLITE_TRANSIENT);
|
||||
if (aid) sqlite3_bind_text(stmt, 8, aid, -1, SQLITE_TRANSIENT);
|
||||
else sqlite3_bind_null(stmt, 8);
|
||||
if (is_lmc) sqlite3_bind_text(stmt, 9, prev_sid_per_contact[ci], -1, SQLITE_TRANSIENT);
|
||||
else sqlite3_bind_null(stmt, 9);
|
||||
if (aid)
|
||||
sqlite3_bind_text(stmt, 8, aid, -1, SQLITE_TRANSIENT);
|
||||
else
|
||||
sqlite3_bind_null(stmt, 8);
|
||||
if (is_lmc)
|
||||
sqlite3_bind_text(stmt, 9, prev_sid_per_contact[ci], -1, SQLITE_TRANSIENT);
|
||||
else
|
||||
sqlite3_bind_null(stmt, 9);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
fprintf(stderr, "seed: insert failed at row %" PRId64 ": %s\n",
|
||||
@@ -309,21 +314,29 @@ db_row_count(const char* db_path)
|
||||
static int64_t
|
||||
db_diff_full(const char* a_path, const char* b_path, int64_t* total_a, int64_t* total_b)
|
||||
{
|
||||
sqlite3* da = NULL; sqlite3* db = NULL;
|
||||
if (sqlite3_open(a_path, &da) != SQLITE_OK) return -1;
|
||||
if (sqlite3_open(b_path, &db) != SQLITE_OK) { sqlite3_close(da); return -1; }
|
||||
sqlite3* da = NULL;
|
||||
sqlite3* db = NULL;
|
||||
if (sqlite3_open(a_path, &da) != SQLITE_OK)
|
||||
return -1;
|
||||
if (sqlite3_open(b_path, &db) != SQLITE_OK) {
|
||||
sqlite3_close(da);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* SQL =
|
||||
"SELECT IFNULL(from_jid,''), IFNULL(to_jid,''), IFNULL(message,''), "
|
||||
"IFNULL(timestamp,''), IFNULL(type,''), IFNULL(stanza_id,''), "
|
||||
"IFNULL(archive_id,''), IFNULL(encryption,''), IFNULL(replace_id,'') "
|
||||
"FROM ChatLogs ORDER BY timestamp ASC, stanza_id ASC;";
|
||||
sqlite3_stmt* sa = NULL; sqlite3_stmt* sb = NULL;
|
||||
const char* SQL = "SELECT IFNULL(from_jid,''), IFNULL(to_jid,''), IFNULL(message,''), "
|
||||
"IFNULL(timestamp,''), IFNULL(type,''), IFNULL(stanza_id,''), "
|
||||
"IFNULL(archive_id,''), IFNULL(encryption,''), IFNULL(replace_id,'') "
|
||||
"FROM ChatLogs ORDER BY timestamp ASC, stanza_id ASC;";
|
||||
sqlite3_stmt* sa = NULL;
|
||||
sqlite3_stmt* sb = NULL;
|
||||
if (sqlite3_prepare_v2(da, SQL, -1, &sa, NULL) != SQLITE_OK
|
||||
|| sqlite3_prepare_v2(db, SQL, -1, &sb, NULL) != SQLITE_OK) {
|
||||
if (sa) sqlite3_finalize(sa);
|
||||
if (sb) sqlite3_finalize(sb);
|
||||
sqlite3_close(da); sqlite3_close(db);
|
||||
if (sa)
|
||||
sqlite3_finalize(sa);
|
||||
if (sb)
|
||||
sqlite3_finalize(sb);
|
||||
sqlite3_close(da);
|
||||
sqlite3_close(db);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -332,14 +345,16 @@ db_diff_full(const char* a_path, const char* b_path, int64_t* total_a, int64_t*
|
||||
while (1) {
|
||||
int ra = sqlite3_step(sa);
|
||||
int rb = sqlite3_step(sb);
|
||||
if (ra != SQLITE_ROW && rb != SQLITE_ROW) break;
|
||||
if (ra == SQLITE_ROW) na++;
|
||||
if (rb == SQLITE_ROW) nb++;
|
||||
if (ra != SQLITE_ROW && rb != SQLITE_ROW)
|
||||
break;
|
||||
if (ra == SQLITE_ROW)
|
||||
na++;
|
||||
if (rb == SQLITE_ROW)
|
||||
nb++;
|
||||
if (ra != rb) {
|
||||
mismatches++;
|
||||
if (shown < 5) {
|
||||
fprintf(stderr, " diff: row count drift at ra_row=%" PRId64
|
||||
" rb_row=%" PRId64 " ra=%d rb=%d\n", na, nb, ra, rb);
|
||||
fprintf(stderr, " diff: row count drift at ra_row=%" PRId64 " rb_row=%" PRId64 " ra=%d rb=%d\n", na, nb, ra, rb);
|
||||
shown++;
|
||||
}
|
||||
continue;
|
||||
@@ -364,8 +379,10 @@ db_diff_full(const char* a_path, const char* b_path, int64_t* total_a, int64_t*
|
||||
sqlite3_finalize(sb);
|
||||
sqlite3_close(da);
|
||||
sqlite3_close(db);
|
||||
if (total_a) *total_a = na;
|
||||
if (total_b) *total_b = nb;
|
||||
if (total_a)
|
||||
*total_a = na;
|
||||
if (total_b)
|
||||
*total_b = nb;
|
||||
return mismatches;
|
||||
}
|
||||
|
||||
@@ -399,15 +416,23 @@ cmd_seed(int argc, char** argv)
|
||||
char* db_path = NULL;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
const char* a = argv[i];
|
||||
if (strncmp(a, "--db=", 5) == 0) db_path = g_strdup(a + 5);
|
||||
else if (strncmp(a, "--rows=", 7) == 0) o.rows = strtoll(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--contacts=", 11) == 0) o.contacts = atoi(a + 11);
|
||||
else if (strncmp(a, "--lmc=", 6) == 0) o.lmc_pct = atoi(a + 6);
|
||||
else if (strncmp(a, "--body=", 7) == 0) o.avg_body = strtoull(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--seed=", 7) == 0) o.seed = strtoull(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--account=", 10) == 0) o.account_jid = a + 10;
|
||||
if (strncmp(a, "--db=", 5) == 0)
|
||||
db_path = g_strdup(a + 5);
|
||||
else if (strncmp(a, "--rows=", 7) == 0)
|
||||
o.rows = strtoll(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--contacts=", 11) == 0)
|
||||
o.contacts = atoi(a + 11);
|
||||
else if (strncmp(a, "--lmc=", 6) == 0)
|
||||
o.lmc_pct = atoi(a + 6);
|
||||
else if (strncmp(a, "--body=", 7) == 0)
|
||||
o.avg_body = strtoull(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--seed=", 7) == 0)
|
||||
o.seed = strtoull(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--account=", 10) == 0)
|
||||
o.account_jid = a + 10;
|
||||
}
|
||||
if (!db_path) db_path = db_path_for(o.account_jid);
|
||||
if (!db_path)
|
||||
db_path = db_path_for(o.account_jid);
|
||||
o.db_path = db_path;
|
||||
int r = do_seed(&o);
|
||||
g_free(db_path);
|
||||
@@ -423,10 +448,14 @@ cmd_export(int argc, char** argv)
|
||||
const char* volume = "export";
|
||||
for (int i = 0; i < argc; i++) {
|
||||
const char* a = argv[i];
|
||||
if (strncmp(a, "--account=", 10) == 0) account = a + 10;
|
||||
else if (strncmp(a, "--csv=", 6) == 0) csv = a + 6;
|
||||
else if (strncmp(a, "--label=", 8) == 0) label = a + 8;
|
||||
else if (strncmp(a, "--volume=", 9) == 0) volume = a + 9;
|
||||
if (strncmp(a, "--account=", 10) == 0)
|
||||
account = a + 10;
|
||||
else if (strncmp(a, "--csv=", 6) == 0)
|
||||
csv = a + 6;
|
||||
else if (strncmp(a, "--label=", 8) == 0)
|
||||
label = a + 8;
|
||||
else if (strncmp(a, "--volume=", 9) == 0)
|
||||
volume = a + 9;
|
||||
}
|
||||
if (!init_sqlite_backend(account))
|
||||
return 2;
|
||||
@@ -461,10 +490,14 @@ cmd_import(int argc, char** argv)
|
||||
const char* volume = "import";
|
||||
for (int i = 0; i < argc; i++) {
|
||||
const char* a = argv[i];
|
||||
if (strncmp(a, "--account=", 10) == 0) account = a + 10;
|
||||
else if (strncmp(a, "--csv=", 6) == 0) csv = a + 6;
|
||||
else if (strncmp(a, "--label=", 8) == 0) label = a + 8;
|
||||
else if (strncmp(a, "--volume=", 9) == 0) volume = a + 9;
|
||||
if (strncmp(a, "--account=", 10) == 0)
|
||||
account = a + 10;
|
||||
else if (strncmp(a, "--csv=", 6) == 0)
|
||||
csv = a + 6;
|
||||
else if (strncmp(a, "--label=", 8) == 0)
|
||||
label = a + 8;
|
||||
else if (strncmp(a, "--volume=", 9) == 0)
|
||||
volume = a + 9;
|
||||
}
|
||||
if (!init_sqlite_backend(account))
|
||||
return 2;
|
||||
@@ -507,24 +540,36 @@ cmd_roundtrip(int argc, char** argv)
|
||||
int do_diff = 0;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
const char* a = argv[i];
|
||||
if (strncmp(a, "--rows=", 7) == 0) rows = strtoll(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--csv=", 6) == 0) csv = a + 6;
|
||||
else if (strncmp(a, "--label=", 8) == 0) label = a + 8;
|
||||
else if (strncmp(a, "--volume=", 9) == 0) volume = a + 9;
|
||||
else if (strncmp(a, "--lmc=", 6) == 0) lmc = atoi(a + 6);
|
||||
else if (strncmp(a, "--body=", 7) == 0) body = strtoull(a + 7, NULL, 10);
|
||||
else if (strcmp(a, "--full-diff") == 0) do_diff = 1;
|
||||
if (strncmp(a, "--rows=", 7) == 0)
|
||||
rows = strtoll(a + 7, NULL, 10);
|
||||
else if (strncmp(a, "--csv=", 6) == 0)
|
||||
csv = a + 6;
|
||||
else if (strncmp(a, "--label=", 8) == 0)
|
||||
label = a + 8;
|
||||
else if (strncmp(a, "--volume=", 9) == 0)
|
||||
volume = a + 9;
|
||||
else if (strncmp(a, "--lmc=", 6) == 0)
|
||||
lmc = atoi(a + 6);
|
||||
else if (strncmp(a, "--body=", 7) == 0)
|
||||
body = strtoull(a + 7, NULL, 10);
|
||||
else if (strcmp(a, "--full-diff") == 0)
|
||||
do_diff = 1;
|
||||
}
|
||||
|
||||
const char* account_a = "rt-a@bench.example";
|
||||
const char* account_b = "rt-b@bench.example";
|
||||
auto_gchar gchar* db_a = db_path_for(account_a);
|
||||
auto_gchar gchar* db_b = db_path_for(account_b);
|
||||
unlink(db_a); unlink(db_b);
|
||||
auto_gchar gchar* wal_a = g_strdup_printf("%s-wal", db_a); unlink(wal_a);
|
||||
auto_gchar gchar* shm_a = g_strdup_printf("%s-shm", db_a); unlink(shm_a);
|
||||
auto_gchar gchar* wal_b = g_strdup_printf("%s-wal", db_b); unlink(wal_b);
|
||||
auto_gchar gchar* shm_b = g_strdup_printf("%s-shm", db_b); unlink(shm_b);
|
||||
unlink(db_a);
|
||||
unlink(db_b);
|
||||
auto_gchar gchar* wal_a = g_strdup_printf("%s-wal", db_a);
|
||||
unlink(wal_a);
|
||||
auto_gchar gchar* shm_a = g_strdup_printf("%s-shm", db_a);
|
||||
unlink(shm_a);
|
||||
auto_gchar gchar* wal_b = g_strdup_printf("%s-wal", db_b);
|
||||
unlink(wal_b);
|
||||
auto_gchar gchar* shm_b = g_strdup_printf("%s-shm", db_b);
|
||||
unlink(shm_b);
|
||||
|
||||
fprintf(stderr, "===== roundtrip: rows=%" PRId64 " account_a=%s account_b=%s =====\n",
|
||||
rows, account_a, account_b);
|
||||
@@ -539,11 +584,13 @@ cmd_roundtrip(int argc, char** argv)
|
||||
so.seed = 42;
|
||||
so.account_jid = account_a;
|
||||
double seed_ms_t0 = bench_now_ms();
|
||||
if (do_seed(&so) != 0) return 2;
|
||||
if (do_seed(&so) != 0)
|
||||
return 2;
|
||||
double seed_ms = bench_now_ms() - seed_ms_t0;
|
||||
|
||||
// 2) Export DB_A → flatlog under account_a
|
||||
if (!init_sqlite_backend(account_a)) return 2;
|
||||
if (!init_sqlite_backend(account_a))
|
||||
return 2;
|
||||
double export_t0 = bench_now_ms();
|
||||
int exported = log_database_export_to_flatfile(NULL);
|
||||
double export_ms = bench_now_ms() - export_t0;
|
||||
@@ -552,7 +599,8 @@ cmd_roundtrip(int argc, char** argv)
|
||||
|
||||
// 3) Cross-mount the flatlog tree at account_b's expected location
|
||||
const char* base = getenv("BENCH_DATA_DIR");
|
||||
if (!base || !base[0]) base = "/tmp/cproof-bench-export";
|
||||
if (!base || !base[0])
|
||||
base = "/tmp/cproof-bench-export";
|
||||
auto_gchar gchar* dir_a = g_strdup_printf("%s/flatlog/%s", base, ff_jid_to_dir(account_a));
|
||||
auto_gchar gchar* dir_b = g_strdup_printf("%s/flatlog/%s", base, ff_jid_to_dir(account_b));
|
||||
auto_gchar gchar* parent_b = g_path_get_dirname(dir_b);
|
||||
@@ -565,7 +613,8 @@ cmd_roundtrip(int argc, char** argv)
|
||||
}
|
||||
|
||||
// 4) Import flatlog → DB_B
|
||||
if (!init_sqlite_backend(account_b)) return 2;
|
||||
if (!init_sqlite_backend(account_b))
|
||||
return 2;
|
||||
double import_t0 = bench_now_ms();
|
||||
int imported = log_database_import_from_flatfile(NULL);
|
||||
double import_ms = bench_now_ms() - import_t0;
|
||||
@@ -583,8 +632,7 @@ cmd_roundtrip(int argc, char** argv)
|
||||
int64_t na = 0, nb = 0;
|
||||
mismatches = db_diff_full(db_a, db_b, &na, &nb);
|
||||
diff_ms = bench_now_ms() - diff_t0;
|
||||
fprintf(stderr, " full content diff: a_rows=%" PRId64 " b_rows=%" PRId64
|
||||
" mismatches=%" PRId64 " (%.2fs)\n",
|
||||
fprintf(stderr, " full content diff: a_rows=%" PRId64 " b_rows=%" PRId64 " mismatches=%" PRId64 " (%.2fs)\n",
|
||||
na, nb, mismatches, diff_ms / 1000.0);
|
||||
}
|
||||
|
||||
@@ -614,13 +662,19 @@ cmd_roundtrip(int argc, char** argv)
|
||||
static int
|
||||
cmd_verify(int argc, char** argv)
|
||||
{
|
||||
const char* a = NULL; const char* b = NULL;
|
||||
const char* a = NULL;
|
||||
const char* b = NULL;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
const char* x = argv[i];
|
||||
if (strncmp(x, "--db-a=", 7) == 0) a = x + 7;
|
||||
else if (strncmp(x, "--db-b=", 7) == 0) b = x + 7;
|
||||
if (strncmp(x, "--db-a=", 7) == 0)
|
||||
a = x + 7;
|
||||
else if (strncmp(x, "--db-b=", 7) == 0)
|
||||
b = x + 7;
|
||||
}
|
||||
if (!a || !b) {
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
if (!a || !b) { usage(); return 2; }
|
||||
int64_t na = 0, nb = 0;
|
||||
int64_t m = db_diff_full(a, b, &na, &nb);
|
||||
fprintf(stderr, "verify: a_rows=%" PRId64 " b_rows=%" PRId64 " mismatches=%" PRId64 "\n",
|
||||
@@ -631,15 +685,23 @@ cmd_verify(int argc, char** argv)
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 2) { usage(); return 2; }
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
const char* sub = argv[1];
|
||||
int sub_argc = argc - 2;
|
||||
char** sub_argv = argv + 2;
|
||||
if (g_strcmp0(sub, "seed") == 0) return cmd_seed(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "export") == 0) return cmd_export(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "import") == 0) return cmd_import(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "roundtrip") == 0) return cmd_roundtrip(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "verify") == 0) return cmd_verify(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "seed") == 0)
|
||||
return cmd_seed(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "export") == 0)
|
||||
return cmd_export(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "import") == 0)
|
||||
return cmd_import(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "roundtrip") == 0)
|
||||
return cmd_roundtrip(sub_argc, sub_argv);
|
||||
if (g_strcmp0(sub, "verify") == 0)
|
||||
return cmd_verify(sub_argc, sub_argv);
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
#include "config/account.h"
|
||||
#include "database_flatfile.h"
|
||||
|
||||
#define ACCOUNT_JID "fail@bench.example"
|
||||
#define CONTACT_JID "peer@bench.example"
|
||||
#define ACCOUNT_JID "fail@bench.example"
|
||||
#define CONTACT_JID "peer@bench.example"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test framework
|
||||
@@ -109,7 +109,8 @@ typedef struct
|
||||
static void
|
||||
verify_summary_free(verify_summary_t* s)
|
||||
{
|
||||
if (!s) return;
|
||||
if (!s)
|
||||
return;
|
||||
g_free(s->first_err);
|
||||
g_free(s->first_warn);
|
||||
}
|
||||
@@ -124,15 +125,18 @@ run_verify(verify_summary_t* out)
|
||||
out->total = g_slist_length(issues);
|
||||
for (GSList* l = issues; l; l = l->next) {
|
||||
integrity_issue_t* i = (integrity_issue_t*)l->data;
|
||||
if (!i) continue;
|
||||
if (!i)
|
||||
continue;
|
||||
switch (i->level) {
|
||||
case INTEGRITY_ERROR:
|
||||
out->errors++;
|
||||
if (!out->first_err) out->first_err = g_strdup(i->message ? i->message : "");
|
||||
if (!out->first_err)
|
||||
out->first_err = g_strdup(i->message ? i->message : "");
|
||||
break;
|
||||
case INTEGRITY_WARNING:
|
||||
out->warnings++;
|
||||
if (!out->first_warn) out->first_warn = g_strdup(i->message ? i->message : "");
|
||||
if (!out->first_warn)
|
||||
out->first_warn = g_strdup(i->message ? i->message : "");
|
||||
break;
|
||||
case INTEGRITY_INFO:
|
||||
out->infos++;
|
||||
@@ -148,7 +152,10 @@ report(fail_ctx_t* ctx, const char* tag, gboolean ok, double wall_ms,
|
||||
{
|
||||
fprintf(stderr, " %s %-3s %.1fms %s\n",
|
||||
ok ? "PASS" : "FAIL", tag, wall_ms, note ? note : "");
|
||||
if (ok) ctx->passed++; else ctx->failed++;
|
||||
if (ok)
|
||||
ctx->passed++;
|
||||
else
|
||||
ctx->failed++;
|
||||
if (ctx->csv_path) {
|
||||
struct stat st;
|
||||
uint64_t sz = (path && stat(path, &st) == 0) ? (uint64_t)st.st_size : 0;
|
||||
@@ -182,7 +189,8 @@ write_normal_line(FILE* fp, int seq, const char* body)
|
||||
static void
|
||||
test_F1(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F1")) return;
|
||||
if (!test_enabled(ctx->tests, "F1"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F1");
|
||||
|
||||
// Write 10 normal lines; chop the trailing \n off the last.
|
||||
@@ -227,7 +235,8 @@ test_F1(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F2(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F2")) return;
|
||||
if (!test_enabled(ctx->tests, "F2"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F2");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
@@ -262,14 +271,17 @@ test_F2(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F3(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F3")) return;
|
||||
if (!test_enabled(ctx->tests, "F3"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F3");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
fprintf(fp, "%s", FLATFILE_HEADER);
|
||||
write_normal_line(fp, 0, "before bom");
|
||||
// Inject BOM bytes at the start of a line in the middle of the file.
|
||||
fputc(0xEF, fp); fputc(0xBB, fp); fputc(0xBF, fp);
|
||||
fputc(0xEF, fp);
|
||||
fputc(0xBB, fp);
|
||||
fputc(0xBF, fp);
|
||||
fprintf(fp,
|
||||
"2025-06-15T12:00:01Z [chat|none|id:after-bom] peer@bench.example/phone: after bom\n");
|
||||
write_normal_line(fp, 2, "trailing");
|
||||
@@ -296,7 +308,8 @@ test_F3(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F7(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F7")) return;
|
||||
if (!test_enabled(ctx->tests, "F7"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F7");
|
||||
|
||||
// A: stanza_id="A", no replace
|
||||
@@ -337,7 +350,8 @@ test_F7(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F8(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F8")) return;
|
||||
if (!test_enabled(ctx->tests, "F8"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F8");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
@@ -376,7 +390,8 @@ test_F8(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F9(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F9")) return;
|
||||
if (!test_enabled(ctx->tests, "F9"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F9");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
@@ -412,7 +427,8 @@ test_F9(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F10(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F10")) return;
|
||||
if (!test_enabled(ctx->tests, "F10"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F10");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
@@ -450,7 +466,8 @@ test_F10(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F11(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F11")) return;
|
||||
if (!test_enabled(ctx->tests, "F11"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F11");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
@@ -488,7 +505,8 @@ test_F11(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F12(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F12")) return;
|
||||
if (!test_enabled(ctx->tests, "F12"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F12");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
@@ -518,7 +536,8 @@ test_F12(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F14(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F14")) return;
|
||||
if (!test_enabled(ctx->tests, "F14"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F14");
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
@@ -563,7 +582,8 @@ test_F14(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F15(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F15")) return;
|
||||
if (!test_enabled(ctx->tests, "F15"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F15");
|
||||
FILE* fp = fopen(path, "w");
|
||||
fclose(fp);
|
||||
@@ -600,7 +620,8 @@ test_F15(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F16(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F16")) return;
|
||||
if (!test_enabled(ctx->tests, "F16"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F16");
|
||||
|
||||
// Write enough messages that we cross at least 3 index entries
|
||||
@@ -715,7 +736,8 @@ test_F16(fail_ctx_t* ctx)
|
||||
static void
|
||||
test_F17(fail_ctx_t* ctx)
|
||||
{
|
||||
if (!test_enabled(ctx->tests, "F17")) return;
|
||||
if (!test_enabled(ctx->tests, "F17"))
|
||||
return;
|
||||
auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F17");
|
||||
|
||||
const int total_msgs = 2000;
|
||||
@@ -812,9 +834,12 @@ main(int argc, char** argv)
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char* a = argv[i];
|
||||
if (strncmp(a, "--tmp=", 6) == 0) ctx.tmp_dir = a + 6;
|
||||
else if (strncmp(a, "--csv=", 6) == 0) ctx.csv_path = a + 6;
|
||||
else if (strncmp(a, "--tests=", 8) == 0) ctx.tests = a + 8;
|
||||
if (strncmp(a, "--tmp=", 6) == 0)
|
||||
ctx.tmp_dir = a + 6;
|
||||
else if (strncmp(a, "--csv=", 6) == 0)
|
||||
ctx.csv_path = a + 6;
|
||||
else if (strncmp(a, "--tests=", 8) == 0)
|
||||
ctx.tests = a + 8;
|
||||
else {
|
||||
fprintf(stderr, "unknown option: %s\n", a);
|
||||
return 2;
|
||||
|
||||
@@ -68,9 +68,12 @@ parse_args(int argc, char** argv, cli_t* c)
|
||||
c->tests = "all";
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char* a = argv[i];
|
||||
if (strncmp(a, "--tmp=", 6) == 0) c->tmp_dir = a + 6;
|
||||
else if (strncmp(a, "--csv=", 6) == 0) c->csv_path = a + 6;
|
||||
else if (strncmp(a, "--tests=", 8) == 0) c->tests = a + 8;
|
||||
if (strncmp(a, "--tmp=", 6) == 0)
|
||||
c->tmp_dir = a + 6;
|
||||
else if (strncmp(a, "--csv=", 6) == 0)
|
||||
c->csv_path = a + 6;
|
||||
else if (strncmp(a, "--tests=", 8) == 0)
|
||||
c->tests = a + 8;
|
||||
else {
|
||||
fprintf(stderr, "unknown option: %s\n", a);
|
||||
return -1;
|
||||
@@ -95,8 +98,7 @@ test_enabled(const char* list, const char* name)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Body factories
|
||||
|
||||
typedef enum
|
||||
{
|
||||
typedef enum {
|
||||
PAT_FILLER, // deterministic alpha-num
|
||||
PAT_EMBEDDED_LF, // filler with newlines every 100 bytes
|
||||
PAT_EMBEDDED_PIPE, // filler with '|' sprinkled
|
||||
@@ -155,7 +157,8 @@ typedef struct
|
||||
static void
|
||||
roundtrip_cleanup(roundtrip_result_t* r)
|
||||
{
|
||||
if (!r) return;
|
||||
if (!r)
|
||||
return;
|
||||
if (r->path && r->path[0])
|
||||
unlink(r->path);
|
||||
g_free(r->path);
|
||||
@@ -167,7 +170,8 @@ write_n_messages(const char* path, int n, size_t body_len, pattern_t pat,
|
||||
int64_t* bytes_written_out)
|
||||
{
|
||||
FILE* fp = fopen(path, "w");
|
||||
if (!fp) return -1;
|
||||
if (!fp)
|
||||
return -1;
|
||||
fprintf(fp, "%s", FLATFILE_HEADER);
|
||||
|
||||
GDateTime* base = g_date_time_new_utc(2025, 6, 15, 12, 0, 0.0);
|
||||
@@ -176,7 +180,7 @@ write_n_messages(const char* path, int n, size_t body_len, pattern_t pat,
|
||||
auto_gchar gchar* iso = g_date_time_format_iso8601(t);
|
||||
g_date_time_unref(t);
|
||||
auto_gchar gchar* sid = g_strdup_printf("long-%d-%08x", i,
|
||||
(unsigned)(rand_r(&(unsigned){i + 1})));
|
||||
(unsigned)(rand_r(&(unsigned){ i + 1 })));
|
||||
auto_gchar gchar* body = make_body(body_len, pat);
|
||||
ff_write_line(fp, iso, "chat", "none",
|
||||
sid, NULL, NULL,
|
||||
@@ -200,7 +204,8 @@ read_and_validate(const char* path, size_t expected_body_len, int n,
|
||||
int64_t* parsed_out, int64_t* mismatched_out, int64_t* parse_fail_out)
|
||||
{
|
||||
FILE* fp = fopen(path, "r");
|
||||
if (!fp) return -1;
|
||||
if (!fp)
|
||||
return -1;
|
||||
ff_skip_bom(fp);
|
||||
|
||||
int64_t parsed = 0, mismatched = 0, failures = 0;
|
||||
@@ -224,9 +229,12 @@ read_and_validate(const char* path, size_t expected_body_len, int n,
|
||||
ff_parsed_line_free(pl);
|
||||
}
|
||||
fclose(fp);
|
||||
if (parsed_out) *parsed_out = parsed;
|
||||
if (mismatched_out) *mismatched_out = mismatched;
|
||||
if (parse_fail_out) *parse_fail_out = failures;
|
||||
if (parsed_out)
|
||||
*parsed_out = parsed;
|
||||
if (mismatched_out)
|
||||
*mismatched_out = mismatched;
|
||||
if (parse_fail_out)
|
||||
*parse_fail_out = failures;
|
||||
(void)n;
|
||||
return 0;
|
||||
}
|
||||
@@ -257,7 +265,8 @@ static void
|
||||
csv_emit(const char* csv_path, const char* tag, size_t body_len, int n,
|
||||
const roundtrip_result_t* r, const char* note)
|
||||
{
|
||||
if (!csv_path) return;
|
||||
if (!csv_path)
|
||||
return;
|
||||
auto_gchar gchar* full_note = g_strdup_printf(
|
||||
"n=%d body=%zu write=%.1fms read=%.1fms parsed=%" PRId64
|
||||
" mismatch=%" PRId64 " parse_fail=%" PRId64 " %s",
|
||||
@@ -337,8 +346,11 @@ run_oversized(const char* tmp, const char* csv)
|
||||
}
|
||||
ff_parsed_line_t* pl = ff_parse_line(buf);
|
||||
free(buf);
|
||||
if (pl) { parsed++; ff_parsed_line_free(pl); }
|
||||
else failures++;
|
||||
if (pl) {
|
||||
parsed++;
|
||||
ff_parsed_line_free(pl);
|
||||
} else
|
||||
failures++;
|
||||
}
|
||||
fclose(fp);
|
||||
double read_ms = bench_now_ms() - t0;
|
||||
@@ -373,7 +385,8 @@ run_pagination_with_huge(const char* tmp, const char* csv)
|
||||
int huge_indices[5] = { 950, 970, 985, 992, 998 }; // five 1MB bodies near end
|
||||
|
||||
FILE* fp = fopen(path, "w");
|
||||
if (!fp) return;
|
||||
if (!fp)
|
||||
return;
|
||||
fprintf(fp, "%s", FLATFILE_HEADER);
|
||||
GDateTime* base = g_date_time_new_utc(2025, 6, 15, 12, 0, 0.0);
|
||||
for (int i = 0; i < total; i++) {
|
||||
@@ -381,7 +394,11 @@ run_pagination_with_huge(const char* tmp, const char* csv)
|
||||
auto_gchar gchar* iso = g_date_time_format_iso8601(t);
|
||||
g_date_time_unref(t);
|
||||
size_t blen = 100;
|
||||
for (int k = 0; k < 5; k++) if (huge_indices[k] == i) { blen = MB; break; }
|
||||
for (int k = 0; k < 5; k++)
|
||||
if (huge_indices[k] == i) {
|
||||
blen = MB;
|
||||
break;
|
||||
}
|
||||
auto_gchar gchar* sid = g_strdup_printf("page-%d", i);
|
||||
auto_gchar gchar* body = make_body(blen, PAT_FILLER);
|
||||
ff_write_line(fp, iso, "chat", "none",
|
||||
@@ -413,12 +430,17 @@ run_pagination_with_huge(const char* tmp, const char* csv)
|
||||
int64_t parsed = 0;
|
||||
char* buf;
|
||||
while ((buf = ff_readline(fp, NULL)) != NULL) {
|
||||
if (buf[0] == '\0' || buf[0] == '#') { free(buf); continue; }
|
||||
if (buf[0] == '\0' || buf[0] == '#') {
|
||||
free(buf);
|
||||
continue;
|
||||
}
|
||||
ff_parsed_line_t* pl = ff_parse_line(buf);
|
||||
free(buf);
|
||||
if (!pl) continue;
|
||||
if (!pl)
|
||||
continue;
|
||||
parsed++;
|
||||
if (ring[rpos]) g_free(ring[rpos]);
|
||||
if (ring[rpos])
|
||||
g_free(ring[rpos]);
|
||||
ring[rpos] = g_strdup(pl->message ? pl->message : "");
|
||||
rpos = (rpos + 1) % 100;
|
||||
ff_parsed_line_free(pl);
|
||||
@@ -426,7 +448,8 @@ run_pagination_with_huge(const char* tmp, const char* csv)
|
||||
fclose(fp);
|
||||
double dt = bench_now_ms() - t0;
|
||||
long rss = bench_peak_rss_kb();
|
||||
for (int i = 0; i < 100; i++) g_free(ring[i]);
|
||||
for (int i = 0; i < 100; i++)
|
||||
g_free(ring[i]);
|
||||
|
||||
fprintf(stderr,
|
||||
" L12 scrollback w/ 5×1MB in last 100 read=%.1fms parsed=%" PRId64 " rss=%ldKB\n",
|
||||
|
||||
@@ -58,7 +58,7 @@ typedef struct
|
||||
const char* csv_path;
|
||||
const char* scenarios; // comma-separated list, or "all"
|
||||
const char* account_jid;
|
||||
int extend_lines; // for S5
|
||||
int extend_lines; // for S5
|
||||
} cli_t;
|
||||
|
||||
static void
|
||||
@@ -77,11 +77,16 @@ parse_args(int argc, char** argv, cli_t* c)
|
||||
cli_default(c);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char* a = argv[i];
|
||||
if (strncmp(a, "--data=", 7) == 0) c->data_dir = a + 7;
|
||||
else if (strncmp(a, "--csv=", 6) == 0) c->csv_path = a + 6;
|
||||
else if (strncmp(a, "--scenarios=", 12) == 0) c->scenarios = a + 12;
|
||||
else if (strncmp(a, "--account=", 10) == 0) c->account_jid = a + 10;
|
||||
else if (strncmp(a, "--extend-lines=", 15) == 0) c->extend_lines = atoi(a + 15);
|
||||
if (strncmp(a, "--data=", 7) == 0)
|
||||
c->data_dir = a + 7;
|
||||
else if (strncmp(a, "--csv=", 6) == 0)
|
||||
c->csv_path = a + 6;
|
||||
else if (strncmp(a, "--scenarios=", 12) == 0)
|
||||
c->scenarios = a + 12;
|
||||
else if (strncmp(a, "--account=", 10) == 0)
|
||||
c->account_jid = a + 10;
|
||||
else if (strncmp(a, "--extend-lines=", 15) == 0)
|
||||
c->extend_lines = atoi(a + 15);
|
||||
else {
|
||||
fprintf(stderr, "unknown option: %s\n", a);
|
||||
return -1;
|
||||
@@ -121,7 +126,8 @@ static int64_t
|
||||
count_lines(const char* path)
|
||||
{
|
||||
FILE* fp = fopen(path, "r");
|
||||
if (!fp) return -1;
|
||||
if (!fp)
|
||||
return -1;
|
||||
int64_t n = 0;
|
||||
int c;
|
||||
while ((c = fgetc(fp)) != EOF) {
|
||||
@@ -191,7 +197,8 @@ run_tail_access(const char* path, int drop_cache, long* peak_rss_kb, int64_t* li
|
||||
ff_contact_state_t* state = ff_state_new(path);
|
||||
if (!ff_state_ensure_fresh(state)) {
|
||||
ff_state_free(state);
|
||||
if (lines_read) *lines_read = 0;
|
||||
if (lines_read)
|
||||
*lines_read = 0;
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
@@ -229,7 +236,8 @@ run_tail_access(const char* path, int drop_cache, long* peak_rss_kb, int64_t* li
|
||||
if (!pl)
|
||||
continue;
|
||||
parsed++;
|
||||
if (ring[ring_pos]) g_free(ring[ring_pos]);
|
||||
if (ring[ring_pos])
|
||||
g_free(ring[ring_pos]);
|
||||
ring[ring_pos] = g_strdup(pl->message ? pl->message : "");
|
||||
ring_pos = (ring_pos + 1) % TAIL_PAGE;
|
||||
ff_parsed_line_free(pl);
|
||||
@@ -297,8 +305,10 @@ run_first_build(const char* path, long* peak_rss_kb, size_t* idx_entries)
|
||||
ff_contact_state_t* state = ff_state_new(path);
|
||||
int ok = ff_state_ensure_fresh(state);
|
||||
double dt = bench_now_ms() - t0;
|
||||
if (idx_entries) *idx_entries = ok ? state->n_entries : 0;
|
||||
if (peak_rss_kb) *peak_rss_kb = bench_peak_rss_kb();
|
||||
if (idx_entries)
|
||||
*idx_entries = ok ? state->n_entries : 0;
|
||||
if (peak_rss_kb)
|
||||
*peak_rss_kb = bench_peak_rss_kb();
|
||||
ff_state_free(state);
|
||||
return ok ? dt : -1.0;
|
||||
}
|
||||
@@ -330,7 +340,8 @@ run_incremental_extend(const char* path, int n_lines, long* peak_rss_kb,
|
||||
int64_t before_size = -1;
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0) before_size = st.st_size;
|
||||
if (stat(path, &st) == 0)
|
||||
before_size = st.st_size;
|
||||
}
|
||||
GDateTime* now = g_date_time_new_now_utc();
|
||||
for (int i = 0; i < n_lines; i++) {
|
||||
@@ -359,7 +370,8 @@ run_incremental_extend(const char* path, int n_lines, long* peak_rss_kb,
|
||||
else
|
||||
*added_bytes = -1;
|
||||
}
|
||||
if (peak_rss_kb) *peak_rss_kb = bench_peak_rss_kb();
|
||||
if (peak_rss_kb)
|
||||
*peak_rss_kb = bench_peak_rss_kb();
|
||||
|
||||
fprintf(stderr, " S5: lines before=%zu after=%zu (delta=%zu)\n",
|
||||
before_lines, state->total_lines,
|
||||
@@ -380,7 +392,8 @@ _count_level(GSList* issues, integrity_level_t level)
|
||||
int n = 0;
|
||||
for (GSList* l = issues; l; l = l->next) {
|
||||
integrity_issue_t* i = (integrity_issue_t*)l->data;
|
||||
if (i && i->level == level) n++;
|
||||
if (i && i->level == level)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -406,7 +419,8 @@ run_verify(long* peak_rss_kb, int64_t* total_issues, int* errors, int* warnings,
|
||||
int shown_err = 0, shown_warn = 0;
|
||||
for (GSList* l = issues; l; l = l->next) {
|
||||
integrity_issue_t* iss = (integrity_issue_t*)l->data;
|
||||
if (!iss) continue;
|
||||
if (!iss)
|
||||
continue;
|
||||
if (iss->level == INTEGRITY_ERROR && shown_err < 5) {
|
||||
fprintf(stderr, " ERR %s:%d %s\n",
|
||||
iss->file ? iss->file : "?", iss->line, iss->message ? iss->message : "");
|
||||
@@ -419,11 +433,16 @@ run_verify(long* peak_rss_kb, int64_t* total_issues, int* errors, int* warnings,
|
||||
}
|
||||
g_slist_free_full(issues, (GDestroyNotify)integrity_issue_free);
|
||||
|
||||
if (peak_rss_kb) *peak_rss_kb = bench_peak_rss_kb();
|
||||
if (total_issues) *total_issues = total;
|
||||
if (errors) *errors = e;
|
||||
if (warnings) *warnings = w;
|
||||
if (infos) *infos = i;
|
||||
if (peak_rss_kb)
|
||||
*peak_rss_kb = bench_peak_rss_kb();
|
||||
if (total_issues)
|
||||
*total_issues = total;
|
||||
if (errors)
|
||||
*errors = e;
|
||||
if (warnings)
|
||||
*warnings = w;
|
||||
if (infos)
|
||||
*infos = i;
|
||||
return dt;
|
||||
}
|
||||
|
||||
@@ -445,7 +464,8 @@ main(int argc, char** argv)
|
||||
cli.data_dir, account_dir);
|
||||
if (!g_file_test(contacts_root, G_FILE_TEST_IS_DIR)) {
|
||||
fprintf(stderr, "ERROR: %s does not exist. Run gen_history first "
|
||||
"(make sure --account matches).\n", contacts_root);
|
||||
"(make sure --account matches).\n",
|
||||
contacts_root);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -454,7 +474,8 @@ main(int argc, char** argv)
|
||||
return 2;
|
||||
|
||||
const char* volume_name = getenv("BENCH_VOLUME");
|
||||
if (!volume_name) volume_name = "medium";
|
||||
if (!volume_name)
|
||||
volume_name = "medium";
|
||||
|
||||
fprintf(stderr, "===== bench_runner: data=%s volume=%s =====\n", cli.data_dir, volume_name);
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#include "config/files.h"
|
||||
#include "config/preferences.h"
|
||||
#include "database.h"
|
||||
#include "database_flatfile.h" // for ff_jid_to_dir() in stub
|
||||
#include "database_flatfile.h" // for ff_jid_to_dir() in stub
|
||||
#include "xmpp/xmpp.h"
|
||||
#include "xmpp/jid.h"
|
||||
#include "xmpp/message.h"
|
||||
@@ -100,14 +100,50 @@ log_error(const char* const msg, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void log_init(log_level_t filter, const char* const log_file) { (void)filter; (void)log_file; }
|
||||
void log_close(void) {}
|
||||
void log_msg(log_level_t level, const char* const area, const char* const msg) { (void)level; (void)area; (void)msg; }
|
||||
const char* get_log_file_location(void) { return ""; }
|
||||
log_level_t log_get_filter(void) { return PROF_LEVEL_INFO; }
|
||||
int log_level_from_string(char* log_level, log_level_t* level) { (void)log_level; if (level) *level = PROF_LEVEL_INFO; return 0; }
|
||||
void log_stderr_init(log_level_t level) { (void)level; }
|
||||
void log_stderr_handler(void) {}
|
||||
void
|
||||
log_init(log_level_t filter, const char* const log_file)
|
||||
{
|
||||
(void)filter;
|
||||
(void)log_file;
|
||||
}
|
||||
void
|
||||
log_close(void)
|
||||
{
|
||||
}
|
||||
void
|
||||
log_msg(log_level_t level, const char* const area, const char* const msg)
|
||||
{
|
||||
(void)level;
|
||||
(void)area;
|
||||
(void)msg;
|
||||
}
|
||||
const char*
|
||||
get_log_file_location(void)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
log_level_t
|
||||
log_get_filter(void)
|
||||
{
|
||||
return PROF_LEVEL_INFO;
|
||||
}
|
||||
int
|
||||
log_level_from_string(char* log_level, log_level_t* level)
|
||||
{
|
||||
(void)log_level;
|
||||
if (level)
|
||||
*level = PROF_LEVEL_INFO;
|
||||
return 0;
|
||||
}
|
||||
void
|
||||
log_stderr_init(log_level_t level)
|
||||
{
|
||||
(void)level;
|
||||
}
|
||||
void
|
||||
log_stderr_handler(void)
|
||||
{
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// prefs / files / xmpp / ui — never reached by the harness, but database_flatfile.c
|
||||
|
||||
@@ -57,16 +57,14 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI options
|
||||
|
||||
typedef enum
|
||||
{
|
||||
typedef enum {
|
||||
SID_UUID,
|
||||
SID_LIBPURPLE,
|
||||
SID_CONVERSATIONS,
|
||||
SID_MIXED,
|
||||
} sid_mode_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
typedef enum {
|
||||
LEN_SHORT,
|
||||
LEN_MIXED,
|
||||
LEN_LONG,
|
||||
@@ -109,20 +107,44 @@ opts_default(opts_t* o)
|
||||
static int
|
||||
parse_sid_mode(const char* s, sid_mode_t* out)
|
||||
{
|
||||
if (g_strcmp0(s, "uuid") == 0) { *out = SID_UUID; return 0; }
|
||||
if (g_strcmp0(s, "libpurple") == 0) { *out = SID_LIBPURPLE; return 0; }
|
||||
if (g_strcmp0(s, "conversations") == 0) { *out = SID_CONVERSATIONS; return 0; }
|
||||
if (g_strcmp0(s, "mixed") == 0) { *out = SID_MIXED; return 0; }
|
||||
if (g_strcmp0(s, "uuid") == 0) {
|
||||
*out = SID_UUID;
|
||||
return 0;
|
||||
}
|
||||
if (g_strcmp0(s, "libpurple") == 0) {
|
||||
*out = SID_LIBPURPLE;
|
||||
return 0;
|
||||
}
|
||||
if (g_strcmp0(s, "conversations") == 0) {
|
||||
*out = SID_CONVERSATIONS;
|
||||
return 0;
|
||||
}
|
||||
if (g_strcmp0(s, "mixed") == 0) {
|
||||
*out = SID_MIXED;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_len_profile(const char* s, len_profile_t* out)
|
||||
{
|
||||
if (g_strcmp0(s, "short") == 0) { *out = LEN_SHORT; return 0; }
|
||||
if (g_strcmp0(s, "mixed") == 0) { *out = LEN_MIXED; return 0; }
|
||||
if (g_strcmp0(s, "long") == 0) { *out = LEN_LONG; return 0; }
|
||||
if (g_strcmp0(s, "extreme") == 0) { *out = LEN_EXTREME; return 0; }
|
||||
if (g_strcmp0(s, "short") == 0) {
|
||||
*out = LEN_SHORT;
|
||||
return 0;
|
||||
}
|
||||
if (g_strcmp0(s, "mixed") == 0) {
|
||||
*out = LEN_MIXED;
|
||||
return 0;
|
||||
}
|
||||
if (g_strcmp0(s, "long") == 0) {
|
||||
*out = LEN_LONG;
|
||||
return 0;
|
||||
}
|
||||
if (g_strcmp0(s, "extreme") == 0) {
|
||||
*out = LEN_EXTREME;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -204,8 +226,26 @@ print_help(void)
|
||||
// Body / stanza-id / resource generators (deterministic)
|
||||
|
||||
static const char* MSG_BANK_TINY[] = {
|
||||
"ok", "yes", "no", "ack", "thx", "ttyl", "k", "lol", "+1", "yep", "nope",
|
||||
"sure", "got it", "great", "afk", "brb", "bye", "hi", "hello", "hey",
|
||||
"ok",
|
||||
"yes",
|
||||
"no",
|
||||
"ack",
|
||||
"thx",
|
||||
"ttyl",
|
||||
"k",
|
||||
"lol",
|
||||
"+1",
|
||||
"yep",
|
||||
"nope",
|
||||
"sure",
|
||||
"got it",
|
||||
"great",
|
||||
"afk",
|
||||
"brb",
|
||||
"bye",
|
||||
"hi",
|
||||
"hello",
|
||||
"hey",
|
||||
};
|
||||
static const int MSG_BANK_TINY_N = sizeof(MSG_BANK_TINY) / sizeof(MSG_BANK_TINY[0]);
|
||||
|
||||
@@ -265,24 +305,35 @@ pick_body_len(uint64_t* rng, len_profile_t profile)
|
||||
return 5 + (xorshift64(rng) % 96);
|
||||
}
|
||||
if (profile == LEN_LONG) {
|
||||
if (r < 200) return 5 + (xorshift64(rng) % 96);
|
||||
if (r < 600) return 100 + (xorshift64(rng) % 400);
|
||||
if (r < 850) return 1000 + (xorshift64(rng) % 9000); // 1-10 KB
|
||||
if (r < 980) return 10000 + (xorshift64(rng) % 90000); // 10-100 KB
|
||||
return 100000 + (xorshift64(rng) % 900000); // 100KB-1MB
|
||||
if (r < 200)
|
||||
return 5 + (xorshift64(rng) % 96);
|
||||
if (r < 600)
|
||||
return 100 + (xorshift64(rng) % 400);
|
||||
if (r < 850)
|
||||
return 1000 + (xorshift64(rng) % 9000); // 1-10 KB
|
||||
if (r < 980)
|
||||
return 10000 + (xorshift64(rng) % 90000); // 10-100 KB
|
||||
return 100000 + (xorshift64(rng) % 900000); // 100KB-1MB
|
||||
}
|
||||
if (profile == LEN_EXTREME) {
|
||||
if (r < 100) return 5 + (xorshift64(rng) % 96);
|
||||
if (r < 400) return 100000 + (xorshift64(rng) % 900000); // 100KB-1MB
|
||||
if (r < 800) return 1000000 + (xorshift64(rng) % 4000000); // 1-5 MB
|
||||
return 5000000 + (xorshift64(rng) % 4000000); // 5-9 MB
|
||||
if (r < 100)
|
||||
return 5 + (xorshift64(rng) % 96);
|
||||
if (r < 400)
|
||||
return 100000 + (xorshift64(rng) % 900000); // 100KB-1MB
|
||||
if (r < 800)
|
||||
return 1000000 + (xorshift64(rng) % 4000000); // 1-5 MB
|
||||
return 5000000 + (xorshift64(rng) % 4000000); // 5-9 MB
|
||||
}
|
||||
// LEN_MIXED — realistic distribution
|
||||
if (r < 500) return 5 + (xorshift64(rng) % 96);
|
||||
if (r < 900) return 100 + (xorshift64(rng) % 400);
|
||||
if (r < 980) return 500 + (xorshift64(rng) % 4500); // 500B-5KB
|
||||
if (r < 995) return 5000 + (xorshift64(rng) % 95000); // 5-100 KB
|
||||
return 100000 + (xorshift64(rng) % 900000); // 100KB-1MB
|
||||
if (r < 500)
|
||||
return 5 + (xorshift64(rng) % 96);
|
||||
if (r < 900)
|
||||
return 100 + (xorshift64(rng) % 400);
|
||||
if (r < 980)
|
||||
return 500 + (xorshift64(rng) % 4500); // 500B-5KB
|
||||
if (r < 995)
|
||||
return 5000 + (xorshift64(rng) % 95000); // 5-100 KB
|
||||
return 100000 + (xorshift64(rng) % 900000); // 100KB-1MB
|
||||
}
|
||||
|
||||
static char*
|
||||
@@ -341,13 +392,15 @@ make_stanza_id(uint64_t* rng, sid_mode_t mode, int64_t global_seq, int contact_i
|
||||
sid_mode_t effective = mode;
|
||||
if (mode == SID_MIXED) {
|
||||
int r = (int)(xorshift64(rng) % 3);
|
||||
effective = (r == 0) ? SID_UUID : (r == 1) ? SID_LIBPURPLE : SID_CONVERSATIONS;
|
||||
effective = (r == 0) ? SID_UUID : (r == 1) ? SID_LIBPURPLE
|
||||
: SID_CONVERSATIONS;
|
||||
}
|
||||
switch (effective) {
|
||||
case SID_LIBPURPLE:
|
||||
// Per-contact incremental — collides across contacts (intentional, mirrors libpurple).
|
||||
return g_strdup_printf("%" PRId64, global_seq);
|
||||
case SID_CONVERSATIONS: {
|
||||
case SID_CONVERSATIONS:
|
||||
{
|
||||
// Conversations-style: 22 base32-ish chars
|
||||
static const char alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
char buf[24];
|
||||
@@ -358,7 +411,8 @@ make_stanza_id(uint64_t* rng, sid_mode_t mode, int64_t global_seq, int contact_i
|
||||
}
|
||||
case SID_UUID:
|
||||
case SID_MIXED:
|
||||
default: {
|
||||
default:
|
||||
{
|
||||
// RFC4122-ish UUID v4 (deterministic from RNG, not cryptographic)
|
||||
uint64_t a = xorshift64(rng);
|
||||
uint64_t b = xorshift64(rng);
|
||||
@@ -379,12 +433,12 @@ make_stanza_id(uint64_t* rng, sid_mode_t mode, int64_t global_seq, int contact_i
|
||||
typedef struct
|
||||
{
|
||||
char* jid;
|
||||
char** resources; // n = opts.resources_per_contact
|
||||
char** resources; // n = opts.resources_per_contact
|
||||
int n_resources;
|
||||
char* dir;
|
||||
char* path;
|
||||
FILE* fp;
|
||||
int64_t seq; // libpurple-style counter
|
||||
int64_t seq; // libpurple-style counter
|
||||
int64_t lines_written;
|
||||
int64_t bytes_written;
|
||||
char* last_stanza_id; // for LMC corrections
|
||||
|
||||
@@ -61,7 +61,10 @@
|
||||
#endif
|
||||
|
||||
/* Macro to wrap each test with setup/teardown functions */
|
||||
#define PROF_FUNC_TEST(test) { #test, test, init_prof_test, close_prof_test, #test }
|
||||
#define PROF_FUNC_TEST(test) \
|
||||
{ \
|
||||
#test, test, init_prof_test, close_prof_test, #test \
|
||||
}
|
||||
|
||||
#ifdef HAVE_SQLITE
|
||||
/* Custom init that sets a non-UTC timezone (POSIX TST-3 = UTC+3) */
|
||||
|
||||
@@ -26,41 +26,41 @@
|
||||
#define PORTS_PER_GROUP 50
|
||||
|
||||
/* Base port and fallback scan range for stabber */
|
||||
#define BASE_PORT 5230
|
||||
#define FALLBACK_PORT_RANGE (TEST_GROUPS * PORTS_PER_GROUP)
|
||||
#define BASE_PORT 5230
|
||||
#define FALLBACK_PORT_RANGE (TEST_GROUPS * PORTS_PER_GROUP)
|
||||
|
||||
/* Shutdown polling: interval and maximum attempts before escalating signals */
|
||||
#define MS_TO_US 1000
|
||||
#define SHUTDOWN_POLL_MS 50
|
||||
#define SHUTDOWN_POLL_MAX 100 /* 100 x 50ms = 5s */
|
||||
#define SIGTERM_POLL_MAX 20 /* 20 x 50ms = 1s */
|
||||
#define QUIT_GRACE_MS 100 /* grace for /quit to be read from pty */
|
||||
#define INIT_WAIT_MS 50 /* wait for child process to initialize */
|
||||
#define INPUT_DELAY_MS 10 /* let profanity process input */
|
||||
#define OUTPUT_POLL_MS 50 /* polling interval for output checks */
|
||||
#define READ_TIMEOUT_MS 100 /* select() timeout for pty reads */
|
||||
#define MS_TO_US 1000
|
||||
#define SHUTDOWN_POLL_MS 50
|
||||
#define SHUTDOWN_POLL_MAX 100 /* 100 x 50ms = 5s */
|
||||
#define SIGTERM_POLL_MAX 20 /* 20 x 50ms = 1s */
|
||||
#define QUIT_GRACE_MS 100 /* grace for /quit to be read from pty */
|
||||
#define INIT_WAIT_MS 50 /* wait for child process to initialize */
|
||||
#define INPUT_DELAY_MS 10 /* let profanity process input */
|
||||
#define OUTPUT_POLL_MS 50 /* polling interval for output checks */
|
||||
#define READ_TIMEOUT_MS 100 /* select() timeout for pty reads */
|
||||
|
||||
/* Expect timeouts (seconds) */
|
||||
#define EXPECT_TIMEOUT_DEFAULT 30
|
||||
#define EXPECT_TIMEOUT_CONNECT 60
|
||||
#define EXPECT_TIMEOUT_DEFAULT 30
|
||||
#define EXPECT_TIMEOUT_CONNECT 60
|
||||
|
||||
/* Test duration thresholds (seconds) */
|
||||
#define SLOW_TEST_THRESHOLD_S 20
|
||||
#define FAST_TEST_THRESHOLD_S 0.3
|
||||
#define SLOW_TEST_THRESHOLD_S 20
|
||||
#define FAST_TEST_THRESHOLD_S 0.3
|
||||
|
||||
/* Sentinel "infinity" for min-elapsed tracking (any real test is faster) */
|
||||
#define MIN_ELAPSED_INIT 1e9
|
||||
#define MIN_ELAPSED_INIT 1e9
|
||||
|
||||
/* Terminal dimensions for forkpty */
|
||||
#define PTY_ROWS 24
|
||||
#define PTY_COLS 300
|
||||
#define PTY_ROWS 24
|
||||
#define PTY_COLS 300
|
||||
|
||||
/* Preprocessor stringification (for setenv from numeric #define) */
|
||||
#define STRINGIFY_(x) #x
|
||||
#define STRINGIFY(x) STRINGIFY_(x)
|
||||
|
||||
char *config_orig;
|
||||
char *data_orig;
|
||||
char* config_orig;
|
||||
char* data_orig;
|
||||
|
||||
int fd = 0;
|
||||
int stub_port = BASE_PORT;
|
||||
@@ -87,19 +87,19 @@ static int expect_timeout = EXPECT_TIMEOUT_DEFAULT;
|
||||
|
||||
/* Per-test wall-clock timer */
|
||||
static struct timespec test_start_ts;
|
||||
static const char *current_test_name;
|
||||
static const char* current_test_name;
|
||||
static double min_elapsed = MIN_ELAPSED_INIT;
|
||||
static const char *min_test_name = "(none)";
|
||||
static const char* min_test_name = "(none)";
|
||||
|
||||
/* Per-test threshold overrides (0 = use default) */
|
||||
double prof_test_slow_threshold = 0;
|
||||
double prof_test_fast_threshold = 0;
|
||||
|
||||
/* Timezone for profanity child process (see proftest.h) */
|
||||
const char *prof_test_tz = "UTC";
|
||||
const char* prof_test_tz = "UTC";
|
||||
|
||||
gboolean
|
||||
_create_dir(const char *name)
|
||||
_create_dir(const char* name)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
@@ -117,14 +117,14 @@ _create_dir(const char *name)
|
||||
}
|
||||
|
||||
gboolean
|
||||
_mkdir_recursive(const char *dir)
|
||||
_mkdir_recursive(const char* dir)
|
||||
{
|
||||
int i;
|
||||
gboolean result = TRUE;
|
||||
|
||||
for (i = 1; i <= strlen(dir); i++) {
|
||||
if (dir[i] == '/' || dir[i] == '\0') {
|
||||
gchar *next_dir = g_strndup(dir, i);
|
||||
gchar* next_dir = g_strndup(dir, i);
|
||||
result = _create_dir(next_dir);
|
||||
g_free(next_dir);
|
||||
if (!result) {
|
||||
@@ -139,7 +139,7 @@ _mkdir_recursive(const char *dir)
|
||||
void
|
||||
_create_config_dir(void)
|
||||
{
|
||||
GString *profanity_dir = g_string_new(xdg_config_home);
|
||||
GString* profanity_dir = g_string_new(xdg_config_home);
|
||||
g_string_append(profanity_dir, "/profanity");
|
||||
|
||||
if (!_mkdir_recursive(profanity_dir->str)) {
|
||||
@@ -152,7 +152,7 @@ _create_config_dir(void)
|
||||
void
|
||||
_create_data_dir(void)
|
||||
{
|
||||
GString *profanity_dir = g_string_new(xdg_data_home);
|
||||
GString* profanity_dir = g_string_new(xdg_data_home);
|
||||
g_string_append(profanity_dir, "/profanity");
|
||||
|
||||
if (!_mkdir_recursive(profanity_dir->str)) {
|
||||
@@ -165,7 +165,7 @@ _create_data_dir(void)
|
||||
void
|
||||
_create_chatlogs_dir(void)
|
||||
{
|
||||
GString *chatlogs_dir = g_string_new(xdg_data_home);
|
||||
GString* chatlogs_dir = g_string_new(xdg_data_home);
|
||||
g_string_append(chatlogs_dir, "/profanity/chatlogs");
|
||||
|
||||
if (!_mkdir_recursive(chatlogs_dir->str)) {
|
||||
@@ -178,7 +178,7 @@ _create_chatlogs_dir(void)
|
||||
void
|
||||
_create_logs_dir(void)
|
||||
{
|
||||
GString *logs_dir = g_string_new(xdg_data_home);
|
||||
GString* logs_dir = g_string_new(xdg_data_home);
|
||||
g_string_append(logs_dir, "/profanity/logs");
|
||||
|
||||
if (!_mkdir_recursive(logs_dir->str)) {
|
||||
@@ -191,12 +191,12 @@ _create_logs_dir(void)
|
||||
void
|
||||
_cleanup_dirs(void)
|
||||
{
|
||||
const char *group_env = getenv("PROF_TEST_GROUP");
|
||||
const char* group_env = getenv("PROF_TEST_GROUP");
|
||||
int group = group_env ? atoi(group_env) : 0;
|
||||
int dir_id = (group >= 1 && group <= TEST_GROUPS) ? group : stub_port;
|
||||
|
||||
|
||||
printf("[PROF_TEST] Cleaning up directories for group %d (dir_id %d)\n", group, dir_id);
|
||||
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf ./test-files/%d", dir_id);
|
||||
int res = system(cmd);
|
||||
@@ -220,26 +220,26 @@ _read_output(int timeout_ms)
|
||||
{
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(fd, &readfds);
|
||||
|
||||
|
||||
tv.tv_sec = timeout_ms / MS_TO_US;
|
||||
tv.tv_usec = (timeout_ms % MS_TO_US) * MS_TO_US;
|
||||
|
||||
|
||||
int ret = select(fd + 1, &readfds, NULL, NULL, &tv);
|
||||
if (ret <= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
size_t space = OUTPUT_BUF_SIZE - output_len - 1;
|
||||
if (space <= 0) {
|
||||
/* Buffer full, shift content */
|
||||
memmove(output_buffer, output_buffer + OUTPUT_BUF_SIZE/2, OUTPUT_BUF_SIZE/2);
|
||||
output_len = OUTPUT_BUF_SIZE/2;
|
||||
memmove(output_buffer, output_buffer + OUTPUT_BUF_SIZE / 2, OUTPUT_BUF_SIZE / 2);
|
||||
output_len = OUTPUT_BUF_SIZE / 2;
|
||||
space = OUTPUT_BUF_SIZE - output_len - 1;
|
||||
}
|
||||
|
||||
|
||||
ssize_t n = read(fd, output_buffer + output_len, space);
|
||||
if (n > 0) {
|
||||
output_len += n;
|
||||
@@ -260,7 +260,7 @@ prof_start(void)
|
||||
ws.ws_col = PTY_COLS;
|
||||
ws.ws_xpixel = 0;
|
||||
ws.ws_ypixel = 0;
|
||||
|
||||
|
||||
/* Reset output buffer */
|
||||
output_len = 0;
|
||||
output_buffer[0] = '\0';
|
||||
@@ -272,48 +272,48 @@ prof_start(void)
|
||||
tzset();
|
||||
|
||||
child_pid = forkpty(&fd, NULL, NULL, &ws);
|
||||
|
||||
|
||||
if (child_pid < 0) {
|
||||
fd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (child_pid == 0) {
|
||||
/* Child process */
|
||||
setenv("COLUMNS", STRINGIFY(PTY_COLS), 1);
|
||||
setenv("TERM", "xterm", 1);
|
||||
|
||||
|
||||
execl("./profanity", "./profanity", "-l", "DEBUG", NULL);
|
||||
|
||||
|
||||
/* If exec fails */
|
||||
fprintf(stderr, "execl failed: %s\n", strerror(errno));
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
|
||||
/* Parent process */
|
||||
/* Set non-blocking mode for reading */
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
|
||||
/* Brief wait for process to initialize */
|
||||
sleep_ms(INIT_WAIT_MS);
|
||||
}
|
||||
|
||||
int
|
||||
init_prof_test(void **state)
|
||||
init_prof_test(void** state)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC, &test_start_ts);
|
||||
current_test_name = state && *state ? (const char *)*state : "unknown";
|
||||
current_test_name = state && *state ? (const char*)*state : "unknown";
|
||||
prof_test_slow_threshold = 0;
|
||||
prof_test_fast_threshold = 0;
|
||||
/* Get test group from environment for static resource allocation */
|
||||
const char *group_env = getenv("PROF_TEST_GROUP");
|
||||
const char* group_env = getenv("PROF_TEST_GROUP");
|
||||
int group = group_env ? atoi(group_env) : 0;
|
||||
|
||||
|
||||
/* Get build index for port offset (for parallel CI builds) */
|
||||
const char *build_env = getenv("PROF_BUILD_INDEX");
|
||||
const char* build_env = getenv("PROF_BUILD_INDEX");
|
||||
int build_idx = build_env ? atoi(build_env) : 0;
|
||||
|
||||
|
||||
/* Calculate port base: each build uses a different range of
|
||||
* TEST_GROUPS * PORTS_PER_GROUP ports.
|
||||
* Build 0 (local/default): 5230-5429, Full: 5230-5429, Minimal: 5430-5629, etc.
|
||||
@@ -321,7 +321,7 @@ init_prof_test(void **state)
|
||||
* or sequential run (no parallel builds), while Full/Minimal/NoEncrypt/Default
|
||||
* are used in CI where they run in parallel. */
|
||||
int port_base = BASE_PORT + ((build_idx > 0 ? build_idx - 1 : 0) * TEST_GROUPS * PORTS_PER_GROUP);
|
||||
|
||||
|
||||
/* Each group gets a dedicated range of ports for parallel execution.
|
||||
* Group 1: port_base..port_base+PORTS_PER_GROUP-1
|
||||
* Group 2: port_base+PORTS_PER_GROUP..port_base+2*PORTS_PER_GROUP-1, etc.
|
||||
@@ -353,7 +353,7 @@ init_prof_test(void **state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!started) {
|
||||
fprintf(stderr, "[PROF_TEST] ERROR: could not start stabber on any port\n");
|
||||
return -1;
|
||||
@@ -366,8 +366,8 @@ init_prof_test(void **state)
|
||||
"./test-files/%d/xdg_config_home", dir_id);
|
||||
snprintf(xdg_data_home, sizeof(xdg_data_home),
|
||||
"./test-files/%d/xdg_data_home", dir_id);
|
||||
|
||||
printf("[PROF_TEST] Group %d using directories: config=%s, data=%s\n",
|
||||
|
||||
printf("[PROF_TEST] Group %d using directories: config=%s, data=%s\n",
|
||||
group, xdg_config_home, xdg_data_home);
|
||||
|
||||
config_orig = getenv("XDG_CONFIG_HOME");
|
||||
@@ -418,7 +418,7 @@ init_prof_test(void **state)
|
||||
}
|
||||
|
||||
int
|
||||
close_prof_test(void **state)
|
||||
close_prof_test(void** state)
|
||||
{
|
||||
if (fd > 0 && child_pid > 0) {
|
||||
prof_input("/quit");
|
||||
@@ -439,7 +439,10 @@ close_prof_test(void **state)
|
||||
if (!exited) {
|
||||
kill(child_pid, SIGTERM);
|
||||
for (int i = 0; i < SIGTERM_POLL_MAX; i++) {
|
||||
if (waitpid(child_pid, NULL, WNOHANG) != 0) { exited = 1; break; }
|
||||
if (waitpid(child_pid, NULL, WNOHANG) != 0) {
|
||||
exited = 1;
|
||||
break;
|
||||
}
|
||||
sleep_ms(SHUTDOWN_POLL_MS);
|
||||
}
|
||||
if (!exited) {
|
||||
@@ -462,7 +465,7 @@ close_prof_test(void **state)
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
double elapsed = (now.tv_sec - test_start_ts.tv_sec)
|
||||
+ (now.tv_nsec - test_start_ts.tv_nsec) / 1e9;
|
||||
+ (now.tv_nsec - test_start_ts.tv_nsec) / 1e9;
|
||||
if (elapsed < min_elapsed) {
|
||||
min_elapsed = elapsed;
|
||||
min_test_name = current_test_name;
|
||||
@@ -483,14 +486,14 @@ close_prof_test(void **state)
|
||||
}
|
||||
|
||||
void
|
||||
prof_input(const char *input)
|
||||
prof_input(const char* input)
|
||||
{
|
||||
GString *inp_str = g_string_new(input);
|
||||
GString* inp_str = g_string_new(input);
|
||||
g_string_append(inp_str, "\r");
|
||||
ssize_t _wn = write(fd, inp_str->str, inp_str->len);
|
||||
(void)_wn;
|
||||
g_string_free(inp_str, TRUE);
|
||||
|
||||
|
||||
/* Small delay to let profanity process input */
|
||||
sleep_ms(INPUT_DELAY_MS);
|
||||
}
|
||||
@@ -500,24 +503,24 @@ prof_input(const char *input)
|
||||
* Returns 1 if found, 0 if timeout.
|
||||
*/
|
||||
int
|
||||
prof_output_exact(const char *text)
|
||||
prof_output_exact(const char* text)
|
||||
{
|
||||
time_t start = time(NULL);
|
||||
|
||||
|
||||
while (time(NULL) - start < expect_timeout) {
|
||||
/* Read any available output */
|
||||
while (_read_output(READ_TIMEOUT_MS) > 0) {
|
||||
/* Keep reading while data available */
|
||||
}
|
||||
|
||||
|
||||
/* Check if text is in buffer */
|
||||
if (strstr(output_buffer, text) != NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sleep_ms(OUTPUT_POLL_MS);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -526,34 +529,34 @@ prof_output_exact(const char *text)
|
||||
* Returns 1 if found, 0 if timeout.
|
||||
*/
|
||||
int
|
||||
prof_output_regex(const char *pattern)
|
||||
prof_output_regex(const char* pattern)
|
||||
{
|
||||
regex_t regex;
|
||||
int ret;
|
||||
|
||||
|
||||
ret = regcomp(®ex, pattern, REG_EXTENDED | REG_NOSUB);
|
||||
if (ret != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
time_t start = time(NULL);
|
||||
|
||||
|
||||
while (time(NULL) - start < expect_timeout) {
|
||||
/* Read any available output */
|
||||
while (_read_output(READ_TIMEOUT_MS) > 0) {
|
||||
/* Keep reading while data available */
|
||||
}
|
||||
|
||||
|
||||
/* Check if pattern matches */
|
||||
ret = regexec(®ex, output_buffer, 0, NULL, 0);
|
||||
if (ret == 0) {
|
||||
regfree(®ex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sleep_ms(OUTPUT_POLL_MS);
|
||||
}
|
||||
|
||||
|
||||
/* Timeout reached - log diagnostic info */
|
||||
fprintf(stderr, "Timeout waiting for regex '%s' after %d seconds. Last output:\n", pattern, expect_timeout);
|
||||
size_t len = strlen(output_buffer);
|
||||
@@ -563,23 +566,21 @@ prof_output_regex(const char *pattern)
|
||||
fprintf(stderr, "%s", output_buffer);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
|
||||
regfree(®ex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
prof_connect_with_roster(const char *roster)
|
||||
prof_connect_with_roster(const char* roster)
|
||||
{
|
||||
GString *roster_str = g_string_new(
|
||||
GString* roster_str = g_string_new(
|
||||
"<iq type='result' to='stabber@localhost/profanity'>"
|
||||
"<query xmlns='jabber:iq:roster' ver='362'>"
|
||||
);
|
||||
"<query xmlns='jabber:iq:roster' ver='362'>");
|
||||
g_string_append(roster_str, roster);
|
||||
g_string_append(roster_str,
|
||||
"</query>"
|
||||
"</iq>"
|
||||
);
|
||||
"</query>"
|
||||
"</iq>");
|
||||
|
||||
stbbr_for_query("jabber:iq:roster", roster_str->str);
|
||||
g_string_free(roster_str, TRUE);
|
||||
@@ -589,7 +590,7 @@ prof_connect_with_roster(const char *roster)
|
||||
char connect_cmd[128];
|
||||
snprintf(connect_cmd, sizeof(connect_cmd), "/connect stabber@localhost server 127.0.0.1 port %d tls disable auth legacy", stub_port);
|
||||
prof_input(connect_cmd);
|
||||
|
||||
|
||||
assert_true(prof_output_regex("password:"));
|
||||
prof_input("password");
|
||||
|
||||
@@ -597,15 +598,14 @@ prof_connect_with_roster(const char *roster)
|
||||
assert_true(prof_output_regex("Connecting as stabber@localhost"));
|
||||
assert_true(prof_output_regex("logged in successfully"));
|
||||
assert_true(prof_output_regex(".+online.+ \\(priority 0\\)\\."));
|
||||
|
||||
|
||||
expect_timeout = EXPECT_TIMEOUT_CONNECT;
|
||||
// Wait for presence stanza to be sent (content-based, not ID-based)
|
||||
// Match the actual attribute order from stanza_attach_caps
|
||||
assert_true(stbbr_received(
|
||||
"<presence id=\"*\">"
|
||||
"<c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"http://profanity-im.github.io\" ver=\"*\"/>"
|
||||
"</presence>"
|
||||
));
|
||||
"<c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"http://profanity-im.github.io\" ver=\"*\"/>"
|
||||
"</presence>"));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -625,6 +625,5 @@ prof_connect(void)
|
||||
{
|
||||
prof_connect_with_roster(
|
||||
"<item jid='buddy1@localhost' subscription='both' name='Buddy1'/>"
|
||||
"<item jid='buddy2@localhost' subscription='both' name='Buddy2'/>"
|
||||
);
|
||||
"<item jid='buddy2@localhost' subscription='both' name='Buddy2'/>");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,6 @@ extern double prof_test_slow_threshold;
|
||||
extern double prof_test_fast_threshold;
|
||||
|
||||
/* Timezone used for profanity child process (default "UTC") */
|
||||
extern const char *prof_test_tz;
|
||||
extern const char* prof_test_tz;
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user