Compare commits
33 Commits
fix/functi
...
fix/functi
| Author | SHA1 | Date | |
|---|---|---|---|
|
737309c888
|
|||
|
0a37dd3fe8
|
|||
|
8e8d715734
|
|||
| 730d46da16 | |||
|
70de0123b0
|
|||
|
569decbbd9
|
|||
|
caac08a7d7
|
|||
|
a179b02f5d
|
|||
|
5e4e28cd13
|
|||
|
4c327bcb0c
|
|||
|
3a77bb1014
|
|||
|
fff902d204
|
|||
|
3307e0eac2
|
|||
|
b02582b19e
|
|||
|
96acaa322d
|
|||
|
c33884ddc4
|
|||
|
b4dd64a0c9
|
|||
| ecf78b26fd | |||
| 0a531ca819 | |||
| 9472662c05 | |||
| 6f94c4bae2 | |||
| e4b6daddf0 | |||
| 036fab364a | |||
| 57e5f30f1a | |||
| ecdf299113 | |||
| 70a1d93821 | |||
| 3685676b0a | |||
| 78bfe2d439 | |||
| d823ca57ce | |||
| 1437aedb2a | |||
| 6f61e5b26c | |||
| c628c2acd5 | |||
| 1305c59b5e |
@@ -138,34 +138,37 @@ You can run the `make spell` command for this.
|
|||||||
`make doublecheck` will run the code formatter, spell checker and unit tests.
|
`make doublecheck` will run the code formatter, spell checker and unit tests.
|
||||||
|
|
||||||
|
|
||||||
### Functional tests
|
### Functional tests: moving away from brittle ID hooks
|
||||||
|
|
||||||
The functional test suite uses [stabber](https://git.jabber.space/devs/stabber) as a mock XMPP server. Tests are located in `tests/functionaltests/`.
|
Historically the functional test suite relied on stabber's id based helpers like `stbbr_for_id("prof_presence_1", ...)` to register canned responses that would be sent once Profanity emitted a stanza carrying that exact `id` attribute. This made the tests fragile:
|
||||||
|
|
||||||
#### Running functional tests
|
* Changes to stanza id generation (sequence, format) broke tests unexpectedly.
|
||||||
|
* Reordering internal requests produced hard-to-debug race conditions when an `id` no longer matched.
|
||||||
|
* Parallel additions of new features could shift which stanzas received a given id causing unrelated test failures.
|
||||||
|
|
||||||
Functional tests require stabber to be installed. Once installed, tests run as part of `make check`:
|
We have migrated to content based stubbing using direct sends (`stbbr_send`) and query hooks (`stbbr_for_query`). Instead of tying a response to a predicted id we now send the required server stanzas explicitly after initiating actions. Example (see `tests/functionaltests/proftest.c`):
|
||||||
|
|
||||||
```bash
|
|
||||||
make check
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Writing functional tests
|
|
||||||
|
|
||||||
Use content-based stubbing with stabber:
|
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Use stbbr_for_query for IQ queries (roster, disco, etc.)
|
// Old brittle approach
|
||||||
stbbr_for_query("jabber:iq:roster", "<iq type='result'>...</iq>");
|
stbbr_for_id("prof_presence_1", "<presence id='prof_presence_1' ...>");
|
||||||
|
|
||||||
// Use stbbr_send for presence, message, and push-style stanzas
|
// New approach: after authentication, send presence directly
|
||||||
stbbr_send("<presence from='buddy@localhost'>...</presence>");
|
stbbr_send("<presence from='stabber@localhost/profanity' to='stabber@localhost/profanity'>...caps...</presence>");
|
||||||
```
|
```
|
||||||
|
|
||||||
Guidelines:
|
Benefits:
|
||||||
|
|
||||||
1. Use `stbbr_for_query(namespace, xml)` for IQ queries where the namespace is stable.
|
* Eliminates dependency on internal id sequencing.
|
||||||
2. Use `stbbr_send(xml)` for presence, message, and other push-style stanzas.
|
* Clearer intent inside test code ("send presence now" vs "register hook and hope client triggers it").
|
||||||
3. Keep assertions tolerant of ordering when possible; use `prof_output_regex()` for flexible matching.
|
* Simplifies adding new tests—no need to inspect logs for generated ids.
|
||||||
4. If timing issues appear, use `prof_timeout()` around critical expectations and reset afterwards.
|
|
||||||
|
Guidelines when writing new functional tests:
|
||||||
|
|
||||||
|
1. Prefer `stbbr_for_query(namespace, xml)` for IQ roster or disco queries where the namespace is stable.
|
||||||
|
2. Use `stbbr_send(xml)` for presence, message, and other push style stanzas.
|
||||||
|
3. Avoid `stbbr_for_id` unless the protocol flow genuinely requires correlating a specific request/response pair not covered by a namespace query.
|
||||||
|
4. Keep assertions tolerant of ordering when possible; rely on `prof_output_regex()` matches rather than hard-coded positions.
|
||||||
|
5. If timing flakiness appears, temporarily raise `prof_timeout()` around the critical expectation and reset it immediately afterwards.
|
||||||
|
|
||||||
|
The migration from `stbbr_for_id` is complete. All functional tests now use content-based stubbing. When adding new tests, follow the guidelines above.
|
||||||
|
|
||||||
|
|||||||
@@ -268,11 +268,7 @@ _show_scrolled(ProfWin* current)
|
|||||||
wattroff(win, bracket_attrs);
|
wattroff(win, bracket_attrs);
|
||||||
|
|
||||||
wattron(win, scrolled_attrs);
|
wattron(win, scrolled_attrs);
|
||||||
if (current->layout->unread_msg == 0) {
|
wprintw(win, "SCROLLED");
|
||||||
wprintw(win, "SCROLLED");
|
|
||||||
} else {
|
|
||||||
wprintw(win, "SCROLLED, NEW MESSAGES");
|
|
||||||
}
|
|
||||||
wattroff(win, scrolled_attrs);
|
wattroff(win, scrolled_attrs);
|
||||||
|
|
||||||
wattron(win, bracket_attrs);
|
wattron(win, bracket_attrs);
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ typedef struct prof_layout_t
|
|||||||
ProfBuff buffer;
|
ProfBuff buffer;
|
||||||
int y_pos;
|
int y_pos;
|
||||||
int paged;
|
int paged;
|
||||||
int unread_msg;
|
|
||||||
} ProfLayout;
|
} ProfLayout;
|
||||||
|
|
||||||
typedef struct prof_layout_simple_t
|
typedef struct prof_layout_simple_t
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ _win_create_simple_layout(void)
|
|||||||
layout->base.buffer = buffer_create();
|
layout->base.buffer = buffer_create();
|
||||||
layout->base.y_pos = 0;
|
layout->base.y_pos = 0;
|
||||||
layout->base.paged = 0;
|
layout->base.paged = 0;
|
||||||
layout->base.unread_msg = 0;
|
|
||||||
scrollok(layout->base.win, TRUE);
|
scrollok(layout->base.win, TRUE);
|
||||||
|
|
||||||
return &layout->base;
|
return &layout->base;
|
||||||
@@ -121,7 +120,6 @@ _win_create_split_layout(void)
|
|||||||
layout->base.buffer = buffer_create();
|
layout->base.buffer = buffer_create();
|
||||||
layout->base.y_pos = 0;
|
layout->base.y_pos = 0;
|
||||||
layout->base.paged = 0;
|
layout->base.paged = 0;
|
||||||
layout->base.unread_msg = 0;
|
|
||||||
scrollok(layout->base.win, TRUE);
|
scrollok(layout->base.win, TRUE);
|
||||||
layout->subwin = NULL;
|
layout->subwin = NULL;
|
||||||
layout->sub_y_pos = 0;
|
layout->sub_y_pos = 0;
|
||||||
@@ -199,7 +197,6 @@ win_create_muc(const char* const roomjid)
|
|||||||
layout->base.buffer = buffer_create();
|
layout->base.buffer = buffer_create();
|
||||||
layout->base.y_pos = 0;
|
layout->base.y_pos = 0;
|
||||||
layout->base.paged = 0;
|
layout->base.paged = 0;
|
||||||
layout->base.unread_msg = 0;
|
|
||||||
scrollok(layout->base.win, TRUE);
|
scrollok(layout->base.win, TRUE);
|
||||||
new_win->window.layout = (ProfLayout*)layout;
|
new_win->window.layout = (ProfLayout*)layout;
|
||||||
|
|
||||||
@@ -701,11 +698,9 @@ void
|
|||||||
win_page_down(ProfWin* window, int scroll_size)
|
win_page_down(ProfWin* window, int scroll_size)
|
||||||
{
|
{
|
||||||
int total_rows = getcury(window->layout->win);
|
int total_rows = getcury(window->layout->win);
|
||||||
int total_rows_with_unread = total_rows + window->layout->unread_msg;
|
|
||||||
int* page_start = &(window->layout->y_pos);
|
int* page_start = &(window->layout->y_pos);
|
||||||
int page_space = getmaxy(stdscr) - 4;
|
int page_space = getmaxy(stdscr) - 4;
|
||||||
int page_start_initial = *page_start;
|
int page_start_initial = *page_start;
|
||||||
|
|
||||||
if (scroll_size == 0)
|
if (scroll_size == 0)
|
||||||
scroll_size = page_space;
|
scroll_size = page_space;
|
||||||
win_scroll_state_t* scroll_state = &window->scroll_state;
|
win_scroll_state_t* scroll_state = &window->scroll_state;
|
||||||
@@ -714,11 +709,7 @@ win_page_down(ProfWin* window, int scroll_size)
|
|||||||
*page_start += scroll_size;
|
*page_start += scroll_size;
|
||||||
|
|
||||||
// Scrolled down after reaching the bottom of the page
|
// Scrolled down after reaching the bottom of the page
|
||||||
gboolean past_bottom = *page_start > total_rows_with_unread - page_space;
|
if ((*page_start > total_rows - page_space || (*page_start == page_space && *page_start >= total_rows)) && window->type == WIN_CHAT) {
|
||||||
gboolean at_page_space_and_past_unread = (*page_start == page_space && *page_start >= total_rows_with_unread);
|
|
||||||
gboolean is_chat = window->type == WIN_CHAT;
|
|
||||||
|
|
||||||
if ((past_bottom || at_page_space_and_past_unread) && is_chat) {
|
|
||||||
int bf_size = buffer_size(window->layout->buffer);
|
int bf_size = buffer_size(window->layout->buffer);
|
||||||
if (bf_size > 0 && *scroll_state != WIN_SCROLL_REACHED_BOTTOM) {
|
if (bf_size > 0 && *scroll_state != WIN_SCROLL_REACHED_BOTTOM) {
|
||||||
// How many lines are left until end of the screen
|
// How many lines are left until end of the screen
|
||||||
@@ -752,16 +743,13 @@ win_page_down(ProfWin* window, int scroll_size)
|
|||||||
window->layout->paged = 1;
|
window->layout->paged = 1;
|
||||||
|
|
||||||
// update only if position has changed
|
// update only if position has changed
|
||||||
if ((page_start_initial != *page_start) || window->layout->unread_msg) {
|
if (page_start_initial != *page_start) {
|
||||||
win_update_virtual(window);
|
win_update_virtual(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Switch off page if no messages left to read.
|
// switch off page if last line and space line visible
|
||||||
* TODO: update buffer end handling to check messages just after last entry.
|
if (total_rows - *page_start == page_space) {
|
||||||
*/
|
|
||||||
if (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) {
|
|
||||||
window->layout->paged = 0;
|
window->layout->paged = 0;
|
||||||
window->layout->unread_msg = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,7 +810,6 @@ win_clear(ProfWin* window)
|
|||||||
int* page_start = &(window->layout->y_pos);
|
int* page_start = &(window->layout->y_pos);
|
||||||
*page_start = y;
|
*page_start = y;
|
||||||
window->layout->paged = 1;
|
window->layout->paged = 1;
|
||||||
window->layout->unread_msg = 0;
|
|
||||||
win_update_virtual(window);
|
win_update_virtual(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -927,7 +914,6 @@ void
|
|||||||
win_move_to_end(ProfWin* window)
|
win_move_to_end(ProfWin* window)
|
||||||
{
|
{
|
||||||
window->layout->paged = 0;
|
window->layout->paged = 0;
|
||||||
window->layout->unread_msg = 0;
|
|
||||||
|
|
||||||
int rows = getmaxy(stdscr);
|
int rows = getmaxy(stdscr);
|
||||||
int y = getcury(window->layout->win);
|
int y = getcury(window->layout->win);
|
||||||
@@ -1710,13 +1696,6 @@ win_newline(ProfWin* window)
|
|||||||
static void
|
static void
|
||||||
_win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...)
|
_win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...)
|
||||||
{
|
{
|
||||||
|
|
||||||
/* Prevent printing and buffer update when user is viewing message history [SCROLLING]*/
|
|
||||||
if (window->layout->paged && wins_is_current(window)) {
|
|
||||||
window->layout->unread_msg++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestamp == NULL) {
|
if (timestamp == NULL) {
|
||||||
timestamp = g_date_time_new_now_local();
|
timestamp = g_date_time_new_now_local();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user