diff --git a/.gitignore b/.gitignore index d3a5ff2b..7c522d80 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ compile_commands.json .project .settings/ .vscode/ +.kilo/ +.roo/ *.plist/ # autotools diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index 234083f5..acab53b5 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -1132,6 +1132,7 @@ cmd_ac_init(void) autocomplete_add(statusbar_show_ac, "name"); autocomplete_add(statusbar_show_ac, "number"); autocomplete_add(statusbar_show_ac, "read"); + autocomplete_add(statusbar_show_ac, "dbbackend"); autocomplete_add(statusbar_tabmode_ac, "actlist"); autocomplete_add(statusbar_tabmode_ac, "dynamic"); @@ -1146,9 +1147,6 @@ cmd_ac_init(void) autocomplete_add(status_state_ac, "xa"); autocomplete_add(status_state_ac, "dnd"); - autocomplete_add(logging_ac, "chat"); - autocomplete_add(logging_ac, "group"); - autocomplete_add(privacy_ac, "logging"); autocomplete_add(privacy_ac, "os"); @@ -4079,11 +4077,6 @@ _logging_autocomplete(ProfWin* window, const char* const input, gboolean previou return result; } - result = autocomplete_param_with_func(input, "/logging chat", prefs_autocomplete_boolean_choice, previous, NULL); - if (result) { - return result; - } - result = autocomplete_param_with_ac(input, "/logging group", logging_group_ac, TRUE, previous); return result; } diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 0f748e33..e89fa62d 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -1289,8 +1289,8 @@ static const struct cmd_t command_defs[] = { CMD_TAGS( CMD_TAG_UI) CMD_SYN( - "/statusbar show name|number|read", - "/statusbar hide name|number|read", + "/statusbar show name|number|read|dbbackend", + "/statusbar hide name|number|read|dbbackend", "/statusbar maxtabs ", "/statusbar tablen ", "/statusbar tabmode default|dynamic|actlist", @@ -1308,6 +1308,7 @@ static const struct cmd_t command_defs[] = { { "show|hide name", "Show or hide names in tabs." }, { "show|hide number", "Show or hide numbers in tabs." }, { "show|hide read", "Show or hide inactive tabs." }, + { "show|hide dbbackend", "Show or hide the database backend indicator [sqlite] or [flatfile]." }, { "self user|barejid|fulljid", "Show account user name, barejid, fulljid as status bar title." }, { "self off", "Disable showing self as status bar title." }, { "chat user|jid", "Show users name, or fulljid. Change needs a redraw/restart to take effect." }, @@ -1659,23 +1660,14 @@ static const struct cmd_t command_defs[] = { }, { CMD_PREAMBLE("/logging", - parse_args, 2, 3, &cons_logging_setting) + parse_args, 0, 0, NULL) CMD_MAINFUNC(cmd_logging) CMD_TAGS( CMD_TAG_CHAT) CMD_SYN( - "/logging chat|group on|off") + "/logging") CMD_DESC( - "Configure chat logging. " - "Switch logging on or off. " - "Chat logging will be enabled if /history is set to on. " - "When disabling this option, /history will also be disabled. ") - CMD_ARGS( - { "chat on|off", "Enable/Disable regular chat logging." }, - { "group on|off", "Enable/Disable groupchat (room) logging." }) - CMD_EXAMPLES( - "/logging chat on", - "/logging group off") + "Deprecated. Use '/history' instead.") }, { CMD_PREAMBLE("/states", @@ -1888,15 +1880,16 @@ static const struct cmd_t command_defs[] = { "/history export []", "/history import []") CMD_DESC( - "Switch chat history on or off, /logging chat will automatically be enabled when this setting is on. " - "When history is enabled, previous messages are shown in chat windows. " + "Enable or disable chat history. " + "When on, messages are persisted via the active database backend and shown in chat windows. " + "When off, persistence stops and old history is hidden on chat-window open. " "Use 'backend' to show the active database backend. " "Use 'switch' to change the active database backend at runtime without reconnecting. " "Use 'verify' to check integrity of stored message history. " "Use 'export' to copy messages from SQLite to flat-file format, or 'import' to copy from flat-file to SQLite. " "Both export and import merge with existing data (duplicates are skipped).") CMD_ARGS( - { "on|off", "Enable or disable showing chat history." }, + { "on|off", "Enable or disable persistence and showing chat history." }, { "backend", "Show the name of the active database backend." }, { "switch sqlite|flatfile", "Switch the active database backend at runtime." }, { "verify []", "Verify integrity of message history. Optionally specify a JID to check only one contact." }, @@ -2741,7 +2734,7 @@ static const struct cmd_t command_defs[] = { "clientid to set the client identification name " "session_alarm to configure an alarm when more clients log in.") CMD_ARGS( - { "logging on|redact|off|flatfile", "Switch chat logging. 'on' uses SQLite database (default). 'flatfile' stores messages as plain text files in ~/.local/share/profanity/flatlog/ that can be manually edited with any text editor. 'off' disables logging entirely. 'redact' stores messages with content replaced by '[redacted]'. Note: 'off' might have unintended consequences, such as not being able to decrypt OMEMO encrypted messages received later via MAM. The 'flatfile' setting takes effect on next connection." }, + { "logging on|redact|off|flatfile", "Switch chat logging backend. 'on' uses the SQLite database (default). 'flatfile' stores messages as plain-text files under ~/.local/share/profanity/flatlog/ that can be edited with any text editor. 'off' disables logging entirely. 'redact' keeps logging but replaces every message body with '[REDACTED]'. Note: 'off' may have side effects such as not being able to decrypt OMEMO messages received later via MAM. Backend changes ('on' / 'flatfile') take effect immediately when connected, otherwise on next connect." }, { "os on|off", "Choose whether to include the OS name if a user asks for software information (XEP-0092)." } ) CMD_EXAMPLES( diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 1600078c..2a49415e 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -6002,6 +6002,12 @@ cmd_statusbar(ProfWin* window, const char* const command, gchar** args) ui_resize(); return TRUE; } + if (g_strcmp0(args[1], "dbbackend") == 0) { + prefs_set_boolean(PREF_STATUSBAR_SHOW_DBBACKEND, TRUE); + cons_show("Enabled showing database backend indicator."); + ui_resize(); + return TRUE; + } cons_bad_cmd_usage(command); return TRUE; } @@ -6035,6 +6041,12 @@ cmd_statusbar(ProfWin* window, const char* const command, gchar** args) ui_resize(); return TRUE; } + if (g_strcmp0(args[1], "dbbackend") == 0) { + prefs_set_boolean(PREF_STATUSBAR_SHOW_DBBACKEND, FALSE); + cons_show("Disabled showing database backend indicator."); + ui_resize(); + return TRUE; + } cons_bad_cmd_usage(command); return TRUE; } @@ -6668,36 +6680,48 @@ cmd_privacy(ProfWin* window, const char* const command, gchar** args) } if (g_strcmp0(args[0], "logging") == 0) { - gchar* arg = args[1]; + const gchar* arg = args[1]; if (arg == NULL) { cons_bad_cmd_usage(command); return TRUE; } - if (g_strcmp0(arg, "on") == 0) { - cons_show("Logging enabled."); - prefs_set_string(PREF_DBLOG, arg); - prefs_set_boolean(PREF_CHLOG, TRUE); - prefs_set_boolean(PREF_HISTORY, TRUE); - } else if (g_strcmp0(arg, "off") == 0) { - cons_show("Logging disabled."); - prefs_set_string(PREF_DBLOG, arg); - prefs_set_boolean(PREF_CHLOG, FALSE); - prefs_set_boolean(PREF_HISTORY, FALSE); - } else if (g_strcmp0(arg, "redact") == 0) { - cons_show("Messages are going to be redacted."); - prefs_set_string(PREF_DBLOG, arg); - } else if (g_strcmp0(arg, "flatfile") == 0) { - auto_gchar gchar* ff_path = files_get_data_path(DIR_FLATLOG); - cons_show("Using flat-file backend for message logging. Takes effect on next connection."); - cons_show("Flatfile directory: %s", ff_path); - prefs_set_string(PREF_DBLOG, arg); - prefs_set_boolean(PREF_CHLOG, TRUE); - prefs_set_boolean(PREF_HISTORY, TRUE); - } else { + const gboolean is_on = (g_strcmp0(arg, "on") == 0); + const gboolean is_off = (g_strcmp0(arg, "off") == 0); + const gboolean is_redact = (g_strcmp0(arg, "redact") == 0); + const gboolean is_flatfile = (g_strcmp0(arg, "flatfile") == 0); + + if (!is_on && !is_off && !is_redact && !is_flatfile) { cons_bad_cmd_usage(command); return TRUE; } + + prefs_set_string(PREF_DBLOG, arg); + prefs_set_boolean(PREF_CHLOG, !is_off); + prefs_set_boolean(PREF_HISTORY, !is_off); + + // For backend-switching values (on = sqlite, flatfile), apply the + // change immediately when connected so the user does not have to + // reconnect to see the new backend take effect. off/redact change + // write semantics but keep the currently-open backend alive. + if ((is_on || is_flatfile) + && active_db_backend + && connection_get_status() == JABBER_CONNECTED + && g_strcmp0(active_db_backend->name, is_on ? "sqlite" : "flatfile") != 0) { + log_database_switch_backend(is_on ? "on" : "flatfile"); + } + + if (is_on) { + cons_show("Database logging enabled (sqlite)."); + } else if (is_off) { + cons_show("Database logging disabled."); + } else if (is_redact) { + cons_show("Database logging set to redact (message bodies replaced with [REDACTED])."); + } else if (is_flatfile) { + auto_gchar gchar* ff_path = files_get_data_path(DIR_FLATLOG); + cons_show("Database logging set to flat-file backend."); + cons_show("Flatfile directory: %s", ff_path); + } return TRUE; } @@ -6707,26 +6731,7 @@ cmd_privacy(ProfWin* window, const char* const command, gchar** args) gboolean cmd_logging(ProfWin* window, const char* const command, gchar** args) { - if (args[0] == NULL) { - cons_logging_setting(); - return TRUE; - } - - if (strcmp(args[0], "chat") == 0 && args[1] != NULL) { - _cmd_set_boolean_preference(args[1], "Chat logging", PREF_CHLOG); - - // if set to off, disable history - if (strcmp(args[1], "off") == 0) { - prefs_set_boolean(PREF_HISTORY, FALSE); - } - - return TRUE; - } else if (g_strcmp0(args[0], "group") == 0 && args[1] != NULL) { - _cmd_set_boolean_preference(args[1], "Groupchat logging", PREF_GRLOG); - return TRUE; - } - - cons_bad_cmd_usage(command); + cons_show("The '/logging' command is deprecated, use '/history' instead."); return TRUE; } @@ -6873,9 +6878,20 @@ cmd_history(ProfWin* window, const char* const command, gchar** args) _cmd_set_boolean_preference(args[0], "Chat history", PREF_HISTORY); - // if set to on, set chlog (/logging chat on) + // /history off should stop persisting messages too, not just hide + // existing history on chat-window open. /history on resumes + // persistence (default backend) and parallel text chatlogs. if (strcmp(args[0], "on") == 0) { prefs_set_boolean(PREF_CHLOG, TRUE); + auto_gchar gchar* dblog = prefs_get_string(PREF_DBLOG); + if (g_strcmp0(dblog, "off") == 0) { + prefs_set_string(PREF_DBLOG, "on"); + cons_show("Database logging re-enabled (sqlite)."); + } + } else if (strcmp(args[0], "off") == 0) { + prefs_set_boolean(PREF_CHLOG, FALSE); + prefs_set_string(PREF_DBLOG, "off"); + cons_show("Database logging disabled."); } return TRUE; @@ -7292,7 +7308,7 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_PGP_LOG, "on"); cons_show("PGP messages will be logged as plaintext."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else if (g_strcmp0(choice, "off") == 0) { prefs_set_string(PREF_PGP_LOG, "off"); @@ -7301,7 +7317,7 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_PGP_LOG, "redact"); cons_show("PGP messages will be logged as '[redacted]'."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else { cons_bad_cmd_usage(command); @@ -7788,7 +7804,7 @@ cmd_ox_log(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_OX_LOG, "on"); cons_show("OX messages will be logged as plaintext."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else if (g_strcmp0(choice, "off") == 0) { prefs_set_string(PREF_OX_LOG, "off"); @@ -7797,7 +7813,7 @@ cmd_ox_log(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_OX_LOG, "redact"); cons_show("OX messages will be logged as '[redacted]'."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else { cons_bad_cmd_usage(command); @@ -7837,7 +7853,7 @@ cmd_otr_log(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_OTR_LOG, "on"); cons_show("OTR messages will be logged as plaintext."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else if (g_strcmp0(choice, "off") == 0) { prefs_set_string(PREF_OTR_LOG, "off"); @@ -7846,7 +7862,7 @@ cmd_otr_log(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_OTR_LOG, "redact"); cons_show("OTR messages will be logged as '[redacted]'."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else { cons_bad_cmd_usage(command); @@ -8783,7 +8799,7 @@ cmd_omemo_log(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_OMEMO_LOG, "on"); cons_show("OMEMO messages will be logged as plaintext."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else if (g_strcmp0(choice, "off") == 0) { prefs_set_string(PREF_OMEMO_LOG, "off"); @@ -8792,7 +8808,7 @@ cmd_omemo_log(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_OMEMO_LOG, "redact"); cons_show("OMEMO messages will be logged as '[redacted]'."); if (!prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + cons_show("Chat logging is currently disabled, use '/history on' to enable."); } } else { cons_bad_cmd_usage(command); diff --git a/src/config/preferences.c b/src/config/preferences.c index 6db29913..96af4af6 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -2208,6 +2208,7 @@ _get_group(preference_t pref) case PREF_STATUSBAR_SHOW_NAME: case PREF_STATUSBAR_SHOW_NUMBER: case PREF_STATUSBAR_SHOW_READ: + case PREF_STATUSBAR_SHOW_DBBACKEND: case PREF_STATUSBAR_SELF: case PREF_STATUSBAR_CHAT: case PREF_STATUSBAR_ROOM_TITLE: @@ -2535,6 +2536,8 @@ _get_key(preference_t pref) return "statusbar.show.number"; case PREF_STATUSBAR_SHOW_READ: return "statusbar.show.read"; + case PREF_STATUSBAR_SHOW_DBBACKEND: + return "statusbar.show.dbbackend"; case PREF_STATUSBAR_SELF: return "statusbar.self"; case PREF_STATUSBAR_CHAT: @@ -2628,6 +2631,7 @@ _get_default_boolean(preference_t pref) case PREF_ROOM_LIST_CACHE: case PREF_STATUSBAR_SHOW_NUMBER: case PREF_STATUSBAR_SHOW_READ: + case PREF_STATUSBAR_SHOW_DBBACKEND: case PREF_REVEAL_OS: case PREF_CORRECTION_ALLOW: case PREF_RECEIPTS_SEND: diff --git a/src/config/preferences.h b/src/config/preferences.h index fffc2082..49ffb900 100644 --- a/src/config/preferences.h +++ b/src/config/preferences.h @@ -162,6 +162,7 @@ typedef enum { PREF_STATUSBAR_SHOW_NAME, PREF_STATUSBAR_SHOW_NUMBER, PREF_STATUSBAR_SHOW_READ, + PREF_STATUSBAR_SHOW_DBBACKEND, PREF_STATUSBAR_SELF, PREF_STATUSBAR_CHAT, PREF_STATUSBAR_ROOM_TITLE, diff --git a/src/database_flatfile.c b/src/database_flatfile.c index ee7d76df..5c70eb08 100644 --- a/src/database_flatfile.c +++ b/src/database_flatfile.c @@ -88,9 +88,8 @@ _ff_cache_line_ids(ff_contact_state_t* state, const char* line) return; // Split metadata on unescaped '|' - char* meta_str = g_strndup(bracket + 1, close - bracket - 1); + auto_gchar gchar* meta_str = g_strndup(bracket + 1, close - bracket - 1); char** parts = ff_split_meta(meta_str); - g_free(meta_str); char* stanza_id = NULL; char* archive_id = NULL; @@ -141,15 +140,13 @@ _ff_maybe_index_line(ff_contact_state_t* state, const char* buf, off_t pos) if (!space) return; - char* ts_str = g_strndup(buf, space - buf); + auto_gchar gchar* ts_str = g_strndup(buf, space - buf); GDateTime* dt = g_date_time_new_from_iso8601(ts_str, NULL); if (!dt) { log_warning("flatfile: unparsable timestamp in %s at offset %ld: %s", state->filepath, (long)pos, ts_str); - g_free(ts_str); return; } - g_free(ts_str); if (state->n_entries >= state->cap_entries) { state->cap_entries = state->cap_entries ? state->cap_entries * 2 : 64; diff --git a/src/ui/console.c b/src/ui/console.c index 6b6ca79d..e27ffd21 100644 --- a/src/ui/console.c +++ b/src/ui/console.c @@ -1884,6 +1884,12 @@ cons_statusbar_setting(void) cons_show("Show tab with no actions (/statusbar) : OFF"); } + if (prefs_get_boolean(PREF_STATUSBAR_SHOW_DBBACKEND)) { + cons_show("Show database backend (/statusbar) : ON"); + } else { + cons_show("Show database backend (/statusbar) : OFF"); + } + cons_show("Max tabs (/statusbar) : %d", prefs_get_statusbartabs()); gint pref_len = prefs_get_statusbartablen(); @@ -1942,27 +1948,12 @@ cons_log_setting(void) cons_show("Log level (/log level) : %s", level); } -void -cons_logging_setting(void) -{ - if (prefs_get_boolean(PREF_CHLOG)) - cons_show("Chat logging (/logging chat) : ON"); - else - cons_show("Chat logging (/logging chat) : OFF"); - - if (prefs_get_boolean(PREF_GRLOG)) - cons_show("Groupchat logging (/logging group) : ON"); - else - cons_show("Groupchat logging (/logging group) : OFF"); -} - void cons_show_log_prefs(void) { cons_show("Logging preferences:"); cons_show(""); cons_log_setting(); - cons_logging_setting(); cons_alert(NULL); } @@ -2856,11 +2847,6 @@ cons_privacy_setting(void) { cons_show("Database logging : %s", prefs_get_string(PREF_DBLOG)); - if (prefs_get_boolean(PREF_CHLOG)) { - cons_show("Chat logging (/logging chat) : ON"); - } else { - cons_show("Chat logging (/logging chat) : OFF"); - } if (prefs_get_boolean(PREF_HISTORY)) { cons_show("Chat history (/history) : ON"); } else { diff --git a/src/ui/statusbar.c b/src/ui/statusbar.c index 6dc157ea..1cb443c3 100644 --- a/src/ui/statusbar.c +++ b/src/ui/statusbar.c @@ -609,6 +609,8 @@ _status_bar_draw_maintext(int pos) static int _status_bar_draw_dbbackend(int pos) { + if (!prefs_get_boolean(PREF_STATUSBAR_SHOW_DBBACKEND)) + return pos; if (!active_db_backend || !active_db_backend->name) return pos; diff --git a/src/ui/ui.h b/src/ui/ui.h index 2420fadf..33c5a4d0 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -330,7 +330,6 @@ void cons_history_setting(void); void cons_carbons_setting(void); void cons_receipts_setting(void); void cons_log_setting(void); -void cons_logging_setting(void); void cons_autoaway_setting(void); void cons_reconnect_setting(void); void cons_autoping_setting(void); diff --git a/src/ui/window.c b/src/ui/window.c index 139a8b88..a41a73fe 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1600,7 +1600,7 @@ win_print_outgoing(ProfWin* window, const char* show_char, const char* const id, GDateTime* timestamp = g_date_time_new_now_local(); const char* myjid = connection_get_fulljid(); - if (!_win_correct(window, message, id, replace_id, myjid)) { + if (!prefs_get_boolean(PREF_CORRECTION_ALLOW) || !_win_correct(window, message, id, replace_id, myjid)) { auto_gchar gchar* outgoing_str = prefs_get_string(PREF_OUTGOING_STAMP); _win_printf(window, show_char, 0, timestamp, 0, THEME_TEXT_ME, outgoing_str, myjid, id, "%s", message); } @@ -1747,7 +1747,7 @@ win_print_outgoing_with_receipt(ProfWin* window, const char* show_char, const ch receipt->received = FALSE; const char* myjid = connection_get_fulljid(); - if (_win_correct(window, message, id, replace_id, myjid)) { + if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && _win_correct(window, message, id, replace_id, myjid)) { free(receipt); // TODO: probably we should use this in _win_correct() } else { int y_start_pos = getcury(window->layout->win); diff --git a/tests/unittests/test_cmd_otr.c b/tests/unittests/test_cmd_otr.c index 7c1850df..39839be6 100644 --- a/tests/unittests/test_cmd_otr.c +++ b/tests/unittests/test_cmd_otr.c @@ -69,7 +69,7 @@ cmd_otr_log_on_shows_warning_when_chlog_disabled(void** state) prefs_set_boolean(PREF_CHLOG, FALSE); expect_cons_show("OTR messages will be logged as plaintext."); - expect_cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + expect_cons_show("Chat logging is currently disabled, use '/history on' to enable."); gboolean result = cmd_otr_log(NULL, CMD_OTR, args); assert_true(result); @@ -115,7 +115,7 @@ cmd_otr_log_redact_shows_warning_when_chlog_disabled(void** state) prefs_set_boolean(PREF_CHLOG, FALSE); expect_cons_show("OTR messages will be logged as '[redacted]'."); - expect_cons_show("Chat logging is currently disabled, use '/logging chat on' to enable."); + expect_cons_show("Chat logging is currently disabled, use '/history on' to enable."); gboolean result = cmd_otr_log(NULL, CMD_OTR, args); assert_true(result);