Files
cproof/tests/functionaltests/proftest.c

343 lines
8.9 KiB
C

#include <sys/stat.h>
#include <sys/wait.h>
#include <glib.h>
#include <stdlib.h>
#include "prof_cmocka.h"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stabber.h>
#include <expect.h>
#include "proftest.h"
char *config_orig;
char *data_orig;
int fd = 0;
int stub_port = 5230;
gboolean
_create_dir(const char *name)
{
struct stat sb;
if (stat(name, &sb) != 0) {
if (errno != ENOENT || mkdir(name, S_IRWXU) != 0) {
return FALSE;
}
} else {
if ((sb.st_mode & S_IFDIR) != S_IFDIR) {
return FALSE;
}
}
return TRUE;
}
gboolean
_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);
result = _create_dir(next_dir);
g_free(next_dir);
if (!result) {
break;
}
}
}
return result;
}
void
_create_config_dir(void)
{
GString *profanity_dir = g_string_new(XDG_CONFIG_HOME);
g_string_append(profanity_dir, "/profanity");
if (!_mkdir_recursive(profanity_dir->str)) {
assert_true(FALSE);
}
g_string_free(profanity_dir, TRUE);
}
void
_create_data_dir(void)
{
GString *profanity_dir = g_string_new(XDG_DATA_HOME);
g_string_append(profanity_dir, "/profanity");
if (!_mkdir_recursive(profanity_dir->str)) {
assert_true(FALSE);
}
g_string_free(profanity_dir, TRUE);
}
void
_create_chatlogs_dir(void)
{
GString *chatlogs_dir = g_string_new(XDG_DATA_HOME);
g_string_append(chatlogs_dir, "/profanity/chatlogs");
if (!_mkdir_recursive(chatlogs_dir->str)) {
assert_true(FALSE);
}
g_string_free(chatlogs_dir, TRUE);
}
void
_create_logs_dir(void)
{
GString *logs_dir = g_string_new(XDG_DATA_HOME);
g_string_append(logs_dir, "/profanity/logs");
if (!_mkdir_recursive(logs_dir->str)) {
assert_true(FALSE);
}
g_string_free(logs_dir, TRUE);
}
void
_cleanup_dirs(void)
{
int res = system("rm -rf ./tests/functionaltests/files");
if (res == -1) {
assert_true(FALSE);
}
}
void
prof_start(void)
{
// helper script sets terminal columns, avoids assertions failing
// based on the test runner terminal size
fd = exp_spawnl("sh",
"sh",
"-c",
"./tests/functionaltests/start_profanity.sh",
NULL);
FILE *fp = fdopen(fd, "r+");
assert_true(fp != NULL);
setbuf(fp, (char *)0);
}
int
init_prof_test(void **state)
{
fprintf(stderr, "\n[TEST] init_prof_test() called\n");
gboolean started = FALSE;
for (int p = 5230; p < 5250; ++p) {
fprintf(stderr, "[TEST] Trying to start stabber on port %d\n", p);
if (stbbr_start(STBBR_LOGDEBUG, p, 0) == 0) {
stub_port = p;
fprintf(stderr, "[TEST] Stabber started successfully on port %d\n", p);
started = TRUE;
break;
}
fprintf(stderr, "[TEST] Port %d failed, trying next\n", p);
}
if (!started) {
assert_true(FALSE); // could not start stabber on any port in range
return -1;
}
config_orig = getenv("XDG_CONFIG_HOME");
data_orig = getenv("XDG_DATA_HOME");
setenv("XDG_CONFIG_HOME", XDG_CONFIG_HOME, 1);
setenv("XDG_DATA_HOME", XDG_DATA_HOME, 1);
_cleanup_dirs();
_create_config_dir();
_create_data_dir();
_create_chatlogs_dir();
_create_logs_dir();
prof_start();
assert_true(prof_output_exact("CProof. Type /help for help information."));
// set UI options to make expect assertions faster and more reliable
prof_input("/inpblock timeout 5");
assert_true(prof_output_exact("Input blocking set to 5 milliseconds"));
prof_input("/inpblock dynamic off");
assert_true(prof_output_exact("Dynamic input blocking disabled"));
prof_input("/notify chat off");
assert_true(prof_output_exact("Chat notifications disabled"));
prof_input("/notify room off");
assert_true(prof_output_exact("Room notifications disabled"));
prof_input("/wrap off");
assert_true(prof_output_exact("Word wrap disabled"));
prof_input("/roster hide");
assert_true(prof_output_exact("Roster disabled"));
prof_input("/occupants default hide");
assert_true(prof_output_exact("Occupant list disabled"));
prof_input("/time console off");
prof_input("/time console off");
assert_true(prof_output_exact("Console time display disabled."));
prof_input("/time chat off");
assert_true(prof_output_exact("Chat time display disabled."));
prof_input("/time muc off");
assert_true(prof_output_exact("MUC time display disabled."));
prof_input("/time config off");
assert_true(prof_output_exact("Config time display disabled."));
prof_input("/time private off");
assert_true(prof_output_exact("Private chat time display disabled."));
prof_input("/time xml off");
assert_true(prof_output_exact("XML Console time display disabled."));
return 0;
}
int
close_prof_test(void **state)
{
prof_input("/quit");
waitpid(exp_pid, NULL, 0);
_cleanup_dirs();
setenv("XDG_CONFIG_HOME", config_orig, 1);
setenv("XDG_DATA_HOME", data_orig, 1);
stbbr_stop();
return 0;
}
void
prof_input(const char *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);
}
int
prof_output_exact(const char *text)
{
return (1 == exp_expectl(fd, exp_exact, text, 1, exp_end));
}
int
prof_output_regex(const char *text)
{
return (1 == exp_expectl(fd, exp_regexp, text, 1, exp_end));
}
void
prof_connect_with_roster(const char *roster)
{
GString *roster_str = g_string_new(
"<iq type='result' to='stabber@localhost/profanity'>"
"<query xmlns='jabber:iq:roster' ver='362'>"
);
g_string_append(roster_str, roster);
g_string_append(roster_str,
"</query>"
"</iq>"
);
stbbr_for_query("jabber:iq:roster", roster_str->str);
g_string_free(roster_str, TRUE);
// Set up stabber to expect password "password"
stbbr_auth_passwd("password");
char connect_cmd[128];
snprintf(connect_cmd, sizeof(connect_cmd), "/connect stabber@localhost server 127.0.0.1 port %d tls disable", stub_port);
fprintf(stderr, "\n[TEST] Sending connect command: %s\n", connect_cmd);
fprintf(stderr, "[TEST] stub_port = %d\n", stub_port);
prof_input(connect_cmd);
// Check password prompt appears
assert_true(prof_output_regex("password:"));
prof_input("password");
// Allow time for profanity to connect
exp_timeout = 60;
// First check: did we get any connection message?
gboolean got_connecting = prof_output_regex("Connecting as stabber@localhost");
gboolean got_success = FALSE;
gboolean got_failure = FALSE;
if (got_connecting) {
fprintf(stderr, "\nDEBUG: Saw 'Connecting as' message, waiting longer for result...\n");
sleep(5); // Give extra time for SASL + bind
// Try to dump whatever is on the screen by sending Enter and reading response
prof_input("");
usleep(500000); // 0.5s
// Now wait for success or failure
got_success = prof_output_regex("logged in successfully");
if (!got_success) {
got_failure = prof_output_regex("failed|Failed|error|Error|disconnect|Disconnect");
if (!got_failure) {
fprintf(stderr, "\nDEBUG: No success or failure after 5s, sending /who to trigger output...\n");
// Send a command to see if connection is actually established
prof_input("/who");
usleep(500000);
got_success = prof_output_regex("logged in successfully|online");
}
}
} else {
// Maybe we went straight to success?
got_success = prof_output_regex("logged in successfully");
}
fprintf(stderr, "\nDEBUG: got_connecting=%d got_success=%d got_failure=%d\n",
got_connecting, got_success, got_failure);
if (got_success) {
assert_true(prof_output_regex(".+online.+ \\(priority 0\\)\\."));
} else {
assert_true(FALSE);
}
exp_timeout = 10;
// Wait for presence stanza to be sent (content-based, not ID-based)
assert_true(stbbr_received(
"<presence id=\"*\">"
"<c hash=\"sha-1\" xmlns=\"http://jabber.org/protocol/caps\" node=\"http://profanity-im.github.io/\" ver=\"*\"/>"
"</presence>"
));
}
void
prof_timeout(int timeout)
{
exp_timeout = timeout;
}
void
prof_timeout_reset(void)
{
exp_timeout = 10;
}
void
prof_connect(void)
{
prof_connect_with_roster(
"<item jid='buddy1@localhost' subscription='both' name='Buddy1'/>"
"<item jid='buddy2@localhost' subscription='both' name='Buddy2'/>"
);
}