Skip to content

Backfill integration tests for post-2019 changes and fix seven bugs they found - #103

Open
MrIron-no wants to merge 9 commits into
UndernetIRC:mainfrom
MrIron-no:feat/backfill-tests
Open

Backfill integration tests for post-2019 changes and fix seven bugs they found#103
MrIron-no wants to merge 9 commits into
UndernetIRC:mainfrom
MrIron-no:feat/backfill-tests

Conversation

@MrIron-no

Copy link
Copy Markdown
Contributor

Summary

Backfills integration tests for the behaviour changes made directly on the release branch since 2019 that had no coverage in the pytest harness, and fixes seven bugs those tests uncovered. Each fix is its own commit and flips the test that documented it (strict xfail) into a passing test.

Tests (337acf9, 50a05e5)

174 new test functions (≈200 items), each module docstring naming the commits it pins down:

Path Covers
tests/chanmodes/ +u part/quit suppression edge cases (op part, &local, empty comment, cross-server quit rewrite, CLEARMODE), +M moderation of unauthenticated users
tests/cap/ (4 new modules) capability list / feature gating, extended-join on every JOIN path (normal, own, +D reveal by message and by +v, kick of a hidden member, server burst, remote join, host-hiding re-JOIN), echo-message for PRIVMSG/NOTICE/channel/WALLCHOPS/nick@server/CPRIVMSG/CNOTICE
tests/relay/ NOTICE nick@server rules, JOIN target limits (JOIN_TARGET), CPRIVMSG idle reset and error numerics
tests/commands/ WHOWAS <nick> 0, WHOX %l, PART, INFO, CONNECT <server> 0, PRIVS defaults, remote STATS parameter forwarding
tests/features/ Boolean feature spellings (0/1/YES/ON…), HIS_REMOTE gating, defaults, removed MAXIMUM_LINKS, bad values, privileges
tests/s2s/ parser robustness (bad numerics, END_OF_BURST full token, junk lines), GLINE reason/lifetime updates from servers
tests/username/ ident lookups only with a username mask, WebIRC username trust and failure modes, STRICT_USERNAME digit-group/case rules
tests/iauth/ STATS iauth / STATS iauthconf, asynchronous ? stats2, concurrent requests, control-character handling
tests/config/ Include directive and the configuration lexer via ircd -k inside the hub container

Harness additions: make_client(caps=...), an oper fixture, docker_exec/docker_cp_text, split P10 handshake methods on P10Server (as on the feature branches), common.py (join/drain/whois/set_featureSET is waited for rather than slept on, because ircu defers a client's commands once its flood penalty builds up). leaf2 now runs a non-forcing IAuth stub (iauth-test.pl, policy ARUS) with ident lookups and a WebIRC port; the hub sets AUTH_TIMEOUT=3, MAXCHANNELSPERUSER=40 and gives notulined.test.net a port. README updated.

Fixes

Commit Bug
209c1fe The hand-coded lexer never emitted FROM, so Include <types> from "file"; was always a syntax error.
8575b05 A missing include file made yylex() return TOKERR forever; ircd -k / REHASH hung. Unopenable inputs are now popped like an empty file.
9b2c888 A self-including (or mutually including) file recursed until the parser stack overflowed and deinit_lexer()'s assert(!yy_in) aborted the daemon. Recursive includes and nesting deeper than 16 are reported instead; deinit_lexer() unwinds.
5ca7fe5 The Include rule required at least one block, so an empty/comment-only include (or a failed one) produced a spurious syntax error.
cd73030 mo_info() still used the stale text[218] offset after m_info/ms_info moved to the "Sources:" marker; opers lost the first source hashes as the file count grew.
d62e82b whisper() never sent the echo-message copy for CPRIVMSG/CNOTICE.
045198e With JOIN_TARGET=FALSE an allowed join past the target budget still got ERR_TARGETTOOFAST (immediately followed by the successful JOIN) and 2 s of penalty each. Added check_target_limit_quiet(); documented JOIN_TARGET in readme.features.

Verification

Full repository suite on this branch (all topologies): 843 passed, 1 error — the error was the oper fixture's fixed nick colliding with a still-pending QUIT, fixed in 50a05e5 and re-verified. New suites alone: 202 passed, 0 xfailed.

Known but left alone (design, not a bug): recent-target tracking hashes the channel pointer and is inherited per IP, so a freshly freed channel's memory reused for a new channel counts as an already-known target.

Add pytest suites for behaviour changes made on the release branch that
had no coverage in the harness (each module docstring names the commits):

  chanmodes/  channel modes +u (part/quit message suppression edge cases)
              and +M (moderate unauthenticated users)
  cap/        capability list, extended-join on every JOIN path,
              echo-message (plus edge cases)
  relay/      NOTICE nick@server rules, JOIN target limits (JOIN_TARGET),
              CPRIVMSG idle reset
  commands/   WHOWAS <nick> 0, WHOX %l field, PART, INFO, CONNECT 0,
              PRIVS defaults, remote STATS parameter forwarding
  features/   Boolean feature spellings (0/1), HIS_REMOTE gating,
              OPLEVELS/ZANNELS defaults, removed MAXIMUM_LINKS
  s2s/        parser robustness (bad numerics, END_OF_BURST token),
              GLINE reason/lifetime updates from servers
  username/   ident lookups only with a username mask, WebIRC username
              trust, STRICT_USERNAME digit-group and case rules
  iauth/      STATS iauth / iauthconf, asynchronous "? stats2",
              control-character handling in IAuth input
  config/     Include directive and lexer via "ircd -k"

Harness changes: make_client(caps=...), an "oper" fixture, docker_exec /
docker_cp_text helpers, split P10 handshake methods on P10Server (as on
the feature branches), and a shared common.py (join/drain/whois and
set_feature, which waits for the RPL_FEATURE reply instead of sleeping
because ircu defers a client's commands once its flood penalty builds
up).  leaf2 now runs a non-forcing IAuth stub (iauth-test.pl) with ident
lookups and a WebIRC port; the hub sets AUTH_TIMEOUT=3 and
MAXCHANNELSPERUSER=40 and gives notulined.test.net a port.

Six strict xfails document bugs found while writing the tests: the
lexer has no FROM token, a missing or empty Include file breaks parsing
(hang / syntax error), a self-including file aborts ircd, mo_info still
uses a stale line offset, and CPRIVMSG/CNOTICE are not echoed.
The grammar accepts "Include <types> from "file";" to restrict which
block types an included file may contain, but the hand-coded lexer
(844238f) never emitted the FROM token, so that form was always a
syntax error.  Add the keyword to the token table.

The restricted-include tests in tests/config/ now pass; a new test
checks that a forbidden block type is reported.
lexer_open() keeps a lex_file whose fd is -1 when an Include target
cannot be opened, and yylex() returned TOKERR for it on every call.
The parser's error recovery never found a token it could resync on, so
"ircd -k" (and a REHASH) spun forever on a missing include file.

Treat an unopenable file like an empty one: pop it and return TEOF (or
end of input if it was the main file) so parsing ends normally with the
"error opening file" diagnostic already reported.
An included file that includes itself (directly or through another
file) recursed until the parser stack overflowed ("memory exhausted"),
after which deinit_lexer()'s assert(!yy_in) aborted the daemon -- for a
REHASH that means the running server dies.

lexer_include() now walks the input stack: a file that is already being
read is reported as "recursive include", and nesting deeper than
MAX_INCLUDE_DEPTH (16) as "include nesting too deep".  In both cases an
input that yields no tokens is pushed so the Include block still ends
with TEOF and parsing continues.  deinit_lexer() unwinds whatever is
left on the stack instead of asserting.
The Include rule required at least one block in the included file
("blocks TEOF"), so a file containing only comments -- or one that
could not be opened -- produced a spurious "syntax error" after the
real diagnostic.  Accept an empty include body.
m_info() and ms_info() were changed to end the public text at the
"Sources:" line instead of a hard-coded offset, but mo_info() still
skipped text[218] entries.  Since the number of hashed source files
has grown, an operator asking "INFO <server>" silently lost the first
part of the hash list (IPcheck.c ... channel.c never appeared) and an
operator without a server argument got no public text at all.

Use the same marker logic in mo_info(): everybody gets the public text,
and operators who name a server get the complete hash list after it.
whisper() delivered the message to the target but, unlike the
PRIVMSG/NOTICE relay paths (and WALLCHOPS/WALLVOICES), never sent the
echo-message copy back to the sender.  Add it after a successful
delivery so clients with the capability see their own CPRIVMSG and
CNOTICE traffic like every other message.
Since 5ffe0a1, JOIN_TARGET=FALSE (the default) lets a client join
channels beyond its free-target budget and defers the target charge to
the first message on the channel.  check_target_join() still called
check_target_limit(), which had already sent ERR_TARGETTOOFAST and
added two seconds to cli_nexttarget before returning: every allowed
join past the budget produced a spurious "Target change too fast" reply
immediately followed by the successful JOIN, and a 25-channel burst
left the client with 30 seconds of extra target penalty.

Split check_target_limit() into a static core with a "report" flag and
add check_target_limit_quiet(), which returns the verdict without
sending the numeric or applying the penalty; use it when JOIN_TARGET is
off.  The JOIN_TARGET=TRUE path is unchanged.  Document JOIN_TARGET in
readme.features, which never listed it.
The fixture always registered as "testop".  ircu defers a client's
commands once its flood penalty builds up, so the previous test's QUIT
could still be pending when the next test registered, which failed with
ERR_NICKNAMEINUSE (seen once in a full-suite run).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant