1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-06 06:20:55 -08:00

Cancel ERC autojoin timer in the server buffer

* lisp/erc/erc-join.el (erc-autojoin-channels-delayed): Only cancel
`erc--autojoin-timer' in the server buffer in which it's local.  After
Emacs 28 brought commit fc66ec3322 "Prefer
defvar-local in erc", customizing `erc-autojoin-timing' to `ident' led
to ERC emitting redundant JOINs before `erc-autojoin--join' was factored
out.  Additionally, don't bother binding options around the call to
`erc-autojoin-channels' to shape its behavior, preferring instead to
call the internal workhorse function directly.  Ever since bug#5521
introduced those options, the logic in `erc-autojoin-channels' has
ignored `erc-autojoin-delay' anyway, so long as `erc-autojoin-timing'
isn't `ident'.  This change may break third party advice.
(erc-autojoin-after-ident): Restore code to cancel timer.  It was
erroneously deleted in 959fbcf34b "favor
network identities in erc-join".
* test/lisp/erc/erc-join-tests.el: Require erc-tests-common atop file so
tests can take advantage of common utilities.
(erc-autojoin-channels--connect): Replace with a function and three
separate tests.
(erc-join-tests--autojoin-channels-connect): New function.
(erc-autojoin-channels/server, erc-autojoin-channels/network)
(erc-autojoin-channels/nomatch): New tests.
(erc-autojoin-channels--delay): Replace with a function and three
separate tests.  Remove guard to skip test on Solaris.
(erc-join-tests--autojoin-channels-ident): New function.
(erc-autojoin-channels-delayed/server)
(erc-autojoin-channels-delayed/network)
(erc-autojoin-channels-delayed/nomatch): New tests.
(erc-autojoin-channels--ident): Replace with function and two separate
tests.
(erc-join-tests--autojoin-after-ident): New function.
(erc-autojoin-after-ident/server)
(erc-autojoin-after-ident/network): New tests.
(erc-join-tests--autojoin-add--common)
(erc-join-tests--autojoin-add): Rename former to latter.
(erc-autojoin-add--network)
(erc-autojoin-add--network-extended-syntax)
(erc-autojoin-add--network-id): Adjust arguments to fixture.
(erc-autojoin-add--server): Use common utils.
(erc-join-tests--autojoin-remove--common)
(erc-join-tests--autojoin-remove): Rename former to latter.
(erc-autojoin-remove--network)
(erc-autojoin-remove--network-id): Adjust args for fixture.
(erc-autojoin-remove--server): Use common utils.
* test/lisp/erc/erc-scenarios-join-timing.el: New file.
* test/lisp/erc/resources/erc-scenarios-common.el
(erc-scenarios-common--run-in-term): In subprocess, don't use failure
tally as exit status unless body form succeeds.
* test/lisp/erc/resources/erc-tests-common.el
(erc-tests-common-init-server-proc): Use `make-process' instead of
`start-process'.
(erc-tests-common-make-server-buf): Use `erc-server-current-nick' to
create the `erc-networks--id' as a `erc-networks--id-qualifying'
instance, which is more realistic than a "fixed" variant.
* test/lisp/erc/resources/join/timing/connect-both.eld: New file.
* test/lisp/erc/resources/join/timing/ident-both.eld: New file.
(Bug#79017)
This commit is contained in:
F. Jason Park 2025-07-17 23:34:05 -07:00
parent 068b324d99
commit 2f5fe1a48e
7 changed files with 469 additions and 302 deletions

View file

@ -124,19 +124,21 @@ servers, presumably in the same domain."
(defvar-local erc--autojoin-timer nil) (defvar-local erc--autojoin-timer nil)
(defun erc-autojoin-channels-delayed (server nick buffer) (defun erc-autojoin-channels-delayed (_ _ buffer)
"Attempt to autojoin channels. "Attempt to autojoin channels in a server BUFFER.
This is called from a timer set up by `erc-autojoin-channels'." Expect to run on a timer after `erc-autojoin-delay' seconds when
(if erc--autojoin-timer `erc-autojoin-timing' is the symbol `ident'."
(setq erc--autojoin-timer (when (buffer-live-p buffer)
(cancel-timer erc--autojoin-timer))) (with-current-buffer buffer
(with-current-buffer buffer (cl-assert (erc--server-buffer-p))
;; Don't kick of another delayed autojoin or try to wait for (when erc--autojoin-timer
;; another ident response: (cancel-timer erc--autojoin-timer)
(let ((erc-autojoin-delay -1) (setq erc--autojoin-timer nil))
(erc-autojoin-timing 'connect)) ;; This log message is likely supposed to indicate that
;; `erc-nickserv-identified-hook' did not yet run, assuming the
;; services module is active.
(erc-log "Delayed autojoin started (no ident success detected yet)") (erc-log "Delayed autojoin started (no ident success detected yet)")
(erc-autojoin-channels server nick)))) (erc-autojoin--join))))
(defun erc-autojoin-server-match (candidate) (defun erc-autojoin-server-match (candidate)
"Match the current network ID or server against CANDIDATE. "Match the current network ID or server against CANDIDATE.
@ -185,8 +187,12 @@ network or a network ID). Return nil on failure."
(defun erc-autojoin-after-ident (_network _nick) (defun erc-autojoin-after-ident (_network _nick)
"Autojoin channels in `erc-autojoin-channels-alist'. "Autojoin channels in `erc-autojoin-channels-alist'.
This function is run from `erc-nickserv-identified-hook'." Expect to run in a server buffer on `erc-nickserv-identified-hook' after
services has authenticated the client."
(when (eq erc-autojoin-timing 'ident) (when (eq erc-autojoin-timing 'ident)
(when erc--autojoin-timer
(cancel-timer erc--autojoin-timer)
(setq erc--autojoin-timer nil))
(erc-autojoin--join))) (erc-autojoin--join)))
(defun erc-autojoin-channels (server nick) (defun erc-autojoin-channels (server nick)

View file

@ -23,342 +23,298 @@
(require 'erc-join) (require 'erc-join)
(require 'erc-networks) (require 'erc-networks)
(ert-deftest erc-autojoin-channels--connect () (eval-and-compile
(let ((load-path (cons (ert-resource-directory) load-path)))
(require 'erc-tests-common)))
;; This merely asserts that `erc-autojoin-channels' sends JOINs right
;; away when the logical connection is established, so long as
;; `erc-autojoin-timing' is the default of `connect'.
(defun erc-join-tests--autojoin-channels-connect (test)
(should (eq erc-autojoin-timing 'connect))
(erc-tests-common-make-server-buf)
(cl-letf* ((calls nil)
(check (lambda () (prog1 calls (setq calls nil))))
((symbol-function 'erc-server-send)
(lambda (line) (push line calls))))
(erc-autojoin-channels erc-server-announced-name "tester")
(funcall test check)
(should-not erc--autojoin-timer))
(when noninteractive
(erc-tests-common-kill-buffers)))
(ert-deftest erc-autojoin-channels/server ()
(let ((erc-autojoin-channels-alist '(("\\.foonet\\.org\\'" "#chan"))))
(erc-join-tests--autojoin-channels-connect
(lambda (check)
(should (equal erc-session-server "irc.foonet.org"))
(should (equal (funcall check) '("JOIN #chan")))))))
(ert-deftest erc-autojoin-channels/network ()
(let ((erc-autojoin-channels-alist '((foonet "#chan"))))
(erc-join-tests--autojoin-channels-connect
(lambda (check)
(should (eq erc-network 'foonet))
(should (equal (funcall check) '("JOIN #chan")))))))
(ert-deftest erc-autojoin-channels/nomatch ()
(let ((erc-autojoin-channels-alist '(("fake\\.foonet\\.org\\'" "#chan")
(barnet "#chan"))))
(erc-join-tests--autojoin-channels-connect
(lambda (check)
(should (eq erc-network 'foonet))
(should (equal erc-server-announced-name "west.foonet.org"))
(should-not (funcall check))))))
;; This doesn't cover the entirety of `erc-autojoin-timing' being
;; `ident'. It only simulates `erc-autojoin-channels-delayed' running
;; `erc-autojoin-delay' seconds after MOTD's end. A JOIN can also be
;; triggered before or after that if `erc-nickserv-identified-hook' runs
;; on a NOTICE login confirmation.
(defun erc-join-tests--autojoin-channels-ident (test)
(should (eq erc-autojoin-timing 'connect)) (should (eq erc-autojoin-timing 'connect))
(should (= erc-autojoin-delay 30)) (should (= erc-autojoin-delay 30))
(should-not erc--autojoin-timer)
(let (calls (erc-tests-common-make-server-buf)
common
erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
(cl-letf (((symbol-function 'erc-server-send) ;; May run forever on Solaris 10 (see bug#79017).
(lambda (line) (push line calls)))) (with-timeout (5 (ert-fail "Timeout exceeded"))
(setq common (cl-letf* ((erc-autojoin-timing 'ident)
(lambda () (erc-autojoin-delay 0.001)
(ert-with-test-buffer (:name "foonet") (timer-list (copy-sequence timer-list))
(erc-mode) (calls nil)
(setq erc-server-process (check (lambda () (prog1 calls (setq calls nil))))
(start-process "true" (current-buffer) "true") ((symbol-function 'erc-server-send)
erc-network 'FooNet (lambda (line) (push line calls)))
erc-session-server "irc.gnu.chat" ((symbol-function 'erc-autojoin-after-ident)
erc-server-current-nick "tester" (lambda (&rest _r) (should-not "run"))))
erc-networks--id (erc-networks--id-create nil)
erc-server-announced-name "foo.gnu.chat")
(set-process-query-on-exit-flag erc-server-process nil)
(erc-autojoin-channels erc-server-announced-name
"tester")
(should-not erc--autojoin-timer))))
(ert-info ("Join immediately on connect; server") (should-not erc--autojoin-timer)
(let ((erc-autojoin-channels-alist '(("\\.gnu\\.chat\\'" "#chan"))))
(funcall common))
(should (equal (pop calls) "JOIN #chan")))
(ert-info ("Join immediately on connect; network") (erc-autojoin-channels erc-server-announced-name "tester")
(let ((erc-autojoin-channels-alist '((FooNet "#chan")))) (should erc--autojoin-timer)
(funcall common)) (sleep-for 0.01)
(should (equal (pop calls) "JOIN #chan"))) (funcall test check)
(ert-info ("Do nothing; server") (should-not calls)
(let ((erc-autojoin-channels-alist '(("bar\\.gnu\\.chat" "#chan")))) (should-not erc--autojoin-timer)))
(funcall common))
(should-not calls))
(ert-info ("Do nothing; network") (when noninteractive
(let ((erc-autojoin-channels-alist '((BarNet "#chan")))) (erc-tests-common-kill-buffers)))
(funcall common))
(should-not calls)))))
(ert-deftest erc-autojoin-channels--delay () (ert-deftest erc-autojoin-channels-delayed/server ()
(let ((erc-autojoin-channels-alist '(("\\.foonet\\.org\\'" "#chan"))))
(erc-join-tests--autojoin-channels-ident
(lambda (check)
(should (equal erc-session-server "irc.foonet.org"))
(should (equal (funcall check) '("JOIN #chan")))))))
(ert-deftest erc-autojoin-channels-delayed/network ()
(let ((erc-autojoin-channels-alist '((foonet "#chan"))))
(erc-join-tests--autojoin-channels-ident
(lambda (check)
(should (eq erc-network 'foonet))
(should (equal (funcall check) '("JOIN #chan")))))))
(ert-deftest erc-autojoin-channels-delayed/nomatch ()
;; Actual announced name is west.foonet.org.
(let ((erc-autojoin-channels-alist '(("east\\.foonet\\.org" "#chan")
(barnet "#chan"))))
(erc-join-tests--autojoin-channels-ident
(lambda (check)
(should (equal erc-server-announced-name "west.foonet.org"))
(should-not (funcall check))))))
;; This asserts that a JOIN is sent on match by the
;; `erc-nickserv-identified-hook' member managed by this module if
;; `erc-autojoin-timing' is set to `ident'.
(defun erc-join-tests--autojoin-after-ident (test)
(should (eq erc-autojoin-timing 'connect)) (should (eq erc-autojoin-timing 'connect))
(should (= erc-autojoin-delay 30)) (should (= erc-autojoin-delay 30))
(should-not erc--autojoin-timer)
(when (eq system-type 'usg-unix-v) (erc-tests-common-make-server-buf)
(ert-skip "Runs forever on Solaris 10 (bug#79017)"))
(let (calls (cl-letf* ((erc-autojoin-timing 'ident)
common (calls nil)
erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook (check (lambda () (prog1 calls (setq calls nil))))
(erc-autojoin-timing 'ident) ((symbol-function 'erc-server-send)
(erc-autojoin-delay 0.05)) (lambda (line) (push line calls))))
(cl-letf (((symbol-function 'erc-server-send) (erc-autojoin-after-ident 'foonet "tester")
(lambda (line) (push line calls))) (funcall test check)
((symbol-function 'erc-autojoin-after-ident) (should-not calls)
(lambda (&rest _r) (error "I ran but shouldn't have")))) (should-not erc--autojoin-timer))
(setq common (when noninteractive
(lambda () (erc-tests-common-kill-buffers)))
(ert-with-test-buffer (:name "foonet")
(erc-mode)
(setq erc-server-process
(start-process "true" (current-buffer) "true")
erc-network 'FooNet
erc-session-server "irc.gnu.chat"
erc-server-current-nick "tester"
erc-networks--id (erc-networks--id-create nil)
erc-server-announced-name "foo.gnu.chat")
(set-process-query-on-exit-flag erc-server-process nil)
(should-not erc--autojoin-timer)
(erc-autojoin-channels erc-server-announced-name "tester")
(should erc--autojoin-timer)
(should-not calls)
(sleep-for 0.1))))
(ert-info ("Deferred on connect; server") (ert-deftest erc-autojoin-after-ident/server ()
(let ((erc-autojoin-channels-alist '(("\\.gnu\\.chat\\'" "#chan")))) (let ((erc-autojoin-channels-alist '(("\\.foonet\\.org\\'" "#chan"))))
(funcall common)) (erc-join-tests--autojoin-after-ident
(should (equal (pop calls) "JOIN #chan"))) (lambda (check)
(should (equal erc-session-server "irc.foonet.org"))
(should (equal (funcall check) '("JOIN #chan")))))))
(ert-info ("Deferred on connect; network") (ert-deftest erc-autojoin-after-ident/network ()
(let ((erc-autojoin-channels-alist '((FooNet "#chan")))) (let ((erc-autojoin-channels-alist '((foonet "#chan"))))
(funcall common)) (erc-join-tests--autojoin-after-ident
(should (equal (pop calls) "JOIN #chan"))) (lambda (check)
(should (eq erc-network 'foonet))
(should (equal (funcall check) '("JOIN #chan")))))))
(ert-info ("Do nothing; server") (defun erc-join-tests--autojoin-add (setup &optional fwd)
(let ((erc-autojoin-channels-alist '(("bar\\.gnu\\.chat" "#chan"))))
(funcall common))
(should-not calls)))))
(ert-deftest erc-autojoin-channels--ident () (erc-tests-common-make-server-buf)
(should (eq erc-autojoin-timing 'connect))
(should (= erc-autojoin-delay 30))
(should-not erc--autojoin-timer)
(let (calls (let ((erc-server-JOIN-functions #'erc-autojoin-add)
common (erc-autojoin-channels-alist nil))
erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook
(erc-autojoin-timing 'ident))
(cl-letf (((symbol-function 'erc-server-send) (puthash 'CHANTYPES '("&#") erc--isupport-params) ; for &local
(lambda (line) (push line calls)))) (funcall setup)
(setq common (ert-info ("Add #chan")
(lambda () (erc-tests-common-simulate-line
(ert-with-test-buffer (:name "foonet") (concat ":tester!~i@c.u JOIN #chan" (and fwd " * :Tes Ter")))
(erc-mode) (should (equal erc-autojoin-channels-alist
(setq erc-server-process '((foonet "#chan")))))
(start-process "true" (current-buffer) "true")
erc-network 'FooNet
erc-server-current-nick "tester"
erc-networks--id (erc-networks--id-create nil)
erc-server-announced-name "foo.gnu.chat")
(set-process-query-on-exit-flag erc-server-process nil)
(erc-autojoin-after-ident 'FooNet "tester")
(should-not erc--autojoin-timer))))
(ert-info ("Join on NickServ hook; server") (ert-info ("Prepends joined chans")
(let ((erc-autojoin-channels-alist '(("\\.gnu\\.chat\\'" "#chan")))) (erc-tests-common-simulate-line ;with account username
(funcall common)) (concat ":tester!~i@c.u JOIN #spam" (and fwd " tester :Tes Ter")))
(should (equal (pop calls) "JOIN #chan"))) (should (equal erc-autojoin-channels-alist
'((foonet "#spam" "#chan")))))
(ert-info ("Join on NickServ hook; network") (ert-info ("Duplicates skipped")
(let ((erc-autojoin-channels-alist '((FooNet "#chan")))) (erc-tests-common-simulate-line
(funcall common)) (concat ":tester!~i@c.u JOIN #chan" (and fwd " * :Tes Ter")))
(should (equal (pop calls) "JOIN #chan")))))) (should (equal erc-autojoin-channels-alist
'((foonet "#spam" "#chan")))))
(defun erc-join-tests--autojoin-add--common (setup &optional fwd) (ert-info ("Announced server used for local channel key")
(let (calls (erc-tests-common-simulate-line
erc-autojoin-channels-alist (concat ":tester!~i@c.u JOIN &local" (and fwd " * :Tes Ter")))
erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) (should (equal erc-autojoin-channels-alist
'(("west\\.foonet\\.org" "&local")
(foonet "#spam" "#chan"))))))
(cl-letf (((symbol-function 'erc-handle-parsed-server-response) (when noninteractive
(lambda (_p m) (push m calls)))) (erc-tests-common-kill-buffers)))
(ert-with-test-buffer (:name "foonet")
(erc-mode)
(setq erc-server-process
(start-process "true" (current-buffer) "true")
erc-server-current-nick "tester"
erc--isupport-params (make-hash-table)
erc-server-announced-name "foo.gnu.chat")
(puthash 'CHANTYPES '("&#") erc--isupport-params)
(funcall setup)
(set-process-query-on-exit-flag erc-server-process nil)
(should-not calls)
(ert-info ("Add #chan")
(erc-parse-server-response erc-server-process
(concat ":tester!~i@c.u JOIN #chan"
(and fwd " * :Tes Ter")))
(should calls)
(erc-autojoin-add erc-server-process (pop calls))
(should (equal erc-autojoin-channels-alist '((FooNet "#chan")))))
(ert-info ("More recently joined chans are prepended")
(erc-parse-server-response
erc-server-process ; with account username
(concat ":tester!~i@c.u JOIN #spam" (and fwd " tester :Tes Ter")))
(should calls)
(erc-autojoin-add erc-server-process (pop calls))
(should (equal erc-autojoin-channels-alist
'((FooNet "#spam" "#chan")))))
(ert-info ("Duplicates skipped")
(erc-parse-server-response erc-server-process
(concat ":tester!~i@c.u JOIN #chan"
(and fwd " * :Tes Ter")))
(should calls)
(erc-autojoin-add erc-server-process (pop calls))
(should (equal erc-autojoin-channels-alist
'((FooNet "#spam" "#chan")))))
(ert-info ("Server used for local channel")
(erc-parse-server-response erc-server-process
(concat ":tester!~i@c.u JOIN &local"
(and fwd " * :Tes Ter")))
(should calls)
(erc-autojoin-add erc-server-process (pop calls))
(should (equal erc-autojoin-channels-alist
'(("foo\\.gnu\\.chat" "&local")
(FooNet "#spam" "#chan")))))))))
(ert-deftest erc-autojoin-add--network () (ert-deftest erc-autojoin-add--network ()
(erc-join-tests--autojoin-add--common (erc-join-tests--autojoin-add #'ignore))
(lambda () (setq erc-network 'FooNet
erc-networks--id (erc-networks--id-create nil)))))
(ert-deftest erc-autojoin-add--network-extended-syntax () (ert-deftest erc-autojoin-add--network-extended-syntax ()
(erc-join-tests--autojoin-add--common (erc-join-tests--autojoin-add #'ignore 'forward-compatible))
(lambda () (setq erc-network 'FooNet
erc-networks--id (erc-networks--id-create nil)))
'forward-compatible))
(ert-deftest erc-autojoin-add--network-id () (ert-deftest erc-autojoin-add--network-id ()
(erc-join-tests--autojoin-add--common (erc-join-tests--autojoin-add
(lambda () (setq erc-network 'invalid (lambda ()
erc-networks--id (erc-networks--id-create 'FooNet))))) (setq erc-network 'invalid
erc-networks--id (erc-networks--id-create 'foonet)))))
;; This shows the fallback behavior for adding alist entries keyed by a
;; domain name (as an unquoted regexp). It runs if the network is not
;; known and the user did not provide an :id keyword to the entry-point
;; command.
(ert-deftest erc-autojoin-add--server () (ert-deftest erc-autojoin-add--server ()
(let (calls
erc-autojoin-channels-alist
erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
(cl-letf (((symbol-function 'erc-handle-parsed-server-response) (erc-tests-common-make-server-buf)
(lambda (_p m) (push m calls))))
(ert-info ("Network unavailable, announced name used") (let ((erc-server-JOIN-functions #'erc-autojoin-add)
(setq erc-autojoin-channels-alist nil) (erc-autojoin-channels-alist nil))
(ert-with-test-buffer (:name "foonet")
(erc-mode)
(setq erc-server-process
(start-process "true" (current-buffer) "true")
erc-server-current-nick "tester"
erc-server-announced-name "foo.gnu.chat"
erc-networks--id (make-erc-networks--id)) ; assume too early
(set-process-query-on-exit-flag erc-server-process nil)
(should-not calls)
(erc-parse-server-response erc-server-process
":tester!~u@q6ddatxcq6txy.irc JOIN #chan")
(should calls)
(erc-autojoin-add erc-server-process (pop calls))
(should (equal erc-autojoin-channels-alist
'(("gnu.chat" "#chan")))))))))
(defun erc-join-tests--autojoin-remove--common (setup) (setq erc-network nil
(let (calls ;; Override the ID so that it's automatically derived instead
erc-autojoin-channels-alist ;; of a "given" one provided by the user.
erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) erc-networks--id (erc-networks--id-create nil))
(cl-letf (((symbol-function 'erc-handle-parsed-server-response) (erc-tests-common-simulate-line
(lambda (_p m) (push m calls)))) ":tester!~u@q6ddatxcq6txy.irc JOIN #chan")
(setq erc-autojoin-channels-alist ; mutated, so can't quote whole thing (should (equal erc-autojoin-channels-alist
(list '(FooNet "#spam" "##chan") '(("foonet.org" "#chan")))))
'(BarNet "#bar" "##bar")
'("foo\\.gnu\\.chat" "&local")))
(ert-with-test-buffer (:name "foonet") (when noninteractive
(erc-mode) (erc-tests-common-kill-buffers)))
(setq erc-server-process
(start-process "true" (current-buffer) "true")
erc-server-current-nick "tester"
erc--isupport-params (make-hash-table)
erc-server-announced-name "foo.gnu.chat")
(puthash 'CHANTYPES '("&#") erc--isupport-params)
(funcall setup)
(set-process-query-on-exit-flag erc-server-process nil)
(should-not calls)
(ert-info ("Remove #chan") (defun erc-join-tests--autojoin-remove (setup)
(erc-parse-server-response erc-server-process
":tester!~i@c.u PART ##chan")
(should calls)
(erc-autojoin-remove erc-server-process (pop calls))
(should (equal erc-autojoin-channels-alist
'((FooNet "#spam")
(BarNet "#bar" "##bar")
("foo\\.gnu\\.chat" "&local")))))
(ert-info ("Wrong network, nothing done") (erc-tests-common-make-server-buf)
(erc-parse-server-response erc-server-process
":tester!~i@c.u PART #bar")
(should calls)
(erc-autojoin-remove erc-server-process (pop calls))
(should (equal erc-autojoin-channels-alist
'((FooNet "#spam")
(BarNet "#bar" "##bar")
("foo\\.gnu\\.chat" "&local")))))
(ert-info ("Local channel keyed by server found") (let ((erc-server-PART-functions #'erc-autojoin-remove)
(erc-parse-server-response erc-server-process (erc-autojoin-channels-alist
":tester!~i@c.u PART &local") (list (list 'foonet "#spam" "##chan")
(should calls) (list 'barnet "#bar")
(erc-autojoin-remove erc-server-process (pop calls)) (list "west\\.foonet\\.org" "&local"))))
(should (equal erc-autojoin-channels-alist
'((FooNet "#spam") (BarNet "#bar" "##bar"))))))))) (puthash 'CHANTYPES '("&#") erc--isupport-params)
(funcall setup)
(ert-info ("Remove #chan")
(erc-tests-common-simulate-line ":tester!~i@c.u PART ##chan")
(should (equal erc-autojoin-channels-alist
'((foonet "#spam")
(barnet "#bar")
("west\\.foonet\\.org" "&local")))))
(ert-info ("Wrong network, nothing done")
(erc-tests-common-simulate-line ":tester!~i@c.u PART #bar")
(should (equal erc-autojoin-channels-alist
'((foonet "#spam")
(barnet "#bar")
("west\\.foonet\\.org" "&local")))))
(ert-info ("Local channel keyed by server found")
(erc-tests-common-simulate-line ":tester!~i@c.u PART &local")
(should (equal erc-autojoin-channels-alist
'((foonet "#spam") (barnet "#bar"))))))
(when noninteractive
(erc-tests-common-kill-buffers)))
(ert-deftest erc-autojoin-remove--network () (ert-deftest erc-autojoin-remove--network ()
(erc-join-tests--autojoin-remove--common (erc-join-tests--autojoin-remove #'ignore))
(lambda () (setq erc-network 'FooNet
erc-networks--id (erc-networks--id-create nil)))))
;; This asserts that a given ID has precedence over the network.
(ert-deftest erc-autojoin-remove--network-id () (ert-deftest erc-autojoin-remove--network-id ()
(erc-join-tests--autojoin-remove--common (erc-join-tests--autojoin-remove
(lambda () (setq erc-network 'fake-a-roo (lambda ()
erc-networks--id (erc-networks--id-create 'FooNet))))) (setq erc-network 'fake
erc-networks--id (erc-networks--id-create "foonet")))))
;; This asserts that domain names are tried if the network is unknown
;; and an explicit ID was not provided on entry-point invocation.
(ert-deftest erc-autojoin-remove--server () (ert-deftest erc-autojoin-remove--server ()
(let (calls
erc-autojoin-channels-alist
erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
(cl-letf (((symbol-function 'erc-handle-parsed-server-response) (erc-tests-common-make-server-buf)
(lambda (_p m) (push m calls))))
(setq erc-autojoin-channels-alist (list '("gnu.chat" "#spam" "##chan") (let ((erc-server-PART-functions #'erc-autojoin-remove)
'("fsf.chat" "#bar" "##bar"))) (erc-autojoin-channels-alist
(list (list "foonet.org" "#spam" "##chan")
(list "fsf.chat" "#bar" "##bar"))))
(ert-with-test-buffer (:name "foonet") (setq erc-network nil
(erc-mode) erc-networks--id (erc-networks--id-create nil))
(setq erc-server-process
(start-process "true" (current-buffer) "true")
erc-server-current-nick "tester"
erc-server-announced-name "foo.gnu.chat"
;; Assume special case without known network
erc-networks--id (make-erc-networks--id))
(set-process-query-on-exit-flag erc-server-process nil)
(should-not calls)
(ert-info ("Announced name matched, #chan removed") (ert-info ("Announced name matched, #chan removed")
(erc-parse-server-response erc-server-process (erc-tests-common-simulate-line ":tester!~i@c.u PART ##chan")
":tester!~i@c.u PART ##chan") (should (equal erc-autojoin-channels-alist
(should calls) '(("foonet.org" "#spam")
(erc-autojoin-remove erc-server-process (pop calls)) ("fsf.chat" "#bar" "##bar")))))
(should (equal erc-autojoin-channels-alist
'(("gnu.chat" "#spam")
("fsf.chat" "#bar" "##bar")))))
(ert-info ("Wrong announced name, nothing done") (ert-info ("Wrong announced name, nothing done")
(erc-parse-server-response erc-server-process (erc-tests-common-simulate-line ":tester!~i@c.u PART #bar")
":tester!~i@c.u PART #bar") (should (equal erc-autojoin-channels-alist
(should calls) '(("foonet.org" "#spam")
(erc-autojoin-remove erc-server-process (pop calls)) ("fsf.chat" "#bar" "##bar"))))))
(should (equal erc-autojoin-channels-alist
'(("gnu.chat" "#spam") (when noninteractive
("fsf.chat" "#bar" "##bar"))))))))) (erc-tests-common-kill-buffers)))
;;; erc-join-tests.el ends here ;;; erc-join-tests.el ends here

View file

@ -0,0 +1,100 @@
;;; erc-scenarios-join-timing.el --- Services integration -*- lexical-binding: t -*-
;; Copyright (C) 2025 Free Software Foundation, Inc.
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; These tests illustrate behavior related to `erc-autojoin-timing'.
;; When the option's value is `ident', the services module runs code on
;; its behalf via `erc-nickserv-identified-hook'.
;; TODO add variants where `erc-nickserv-identify-mode' is not `both'.
;;; Code:
(require 'ert-x)
(eval-and-compile
(let ((load-path (cons (ert-resource-directory) load-path)))
(require 'erc-scenarios-common)))
(require 'erc-join)
(require 'erc-services)
(defun erc-scenarios-join-timing--services-identify-both (dialog)
(should (eq erc-nickserv-identify-mode 'both))
(erc-scenarios-common-with-cleanup
((erc-scenarios-common-dialog "join/timing")
(erc-server-flood-penalty 0.1)
(erc-modules (cons 'services erc-modules))
(erc-autojoin-channels-alist '((Libera.Chat "#chan")))
(erc-nickserv-passwords '((Libera.Chat (("tester" . "changeme")))))
(expect (erc-d-t-make-expecter))
(dumb-server (erc-d-run "localhost" t dialog))
(port (process-contact dumb-server :service)))
(ert-info ("Connect without password")
(with-current-buffer (erc :server "127.0.0.1"
:port port
:nick "tester"
:user "tester"
:full-name "tester")
(should (string= (buffer-name) (format "127.0.0.1:%d" port)))
(erc-d-t-wait-for 5 (eq erc-network 'Libera.Chat))
(funcall expect 5 "This nickname is registered.")
(with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
(funcall expect 10 "created"))
(funcall expect 2 "You are now identified")
(funcall expect 2 "You are now logged in as tester")))
(erc-services-mode -1)
(should-not (memq 'services erc-modules))))
;; This asserts the default behavior, when `erc-autojoin-timing' is
;; `connect'. Here, ERC emits the JOIN request before being informed by
;; NickServ that it needs to log in. The server then holds off on
;; granting the JOIN until authentication has completed.
(ert-deftest erc-scenarios-join-timing/connect ()
:tags '(:expensive-test)
(should (eq erc-autojoin-timing 'connect))
(erc-scenarios-join-timing--services-identify-both 'connect-both))
;; This demos the integration between the `join' and `services' modules
;; that occurs when `erc-autojoin-timing' is `ident'. Here, ERC
;; arranges not to request a JOIN until it's been authenticated by the
;; server. Since `erc-autojoin-delay' remains at its default of 30,
;; authentication occurs before the fallback timer fires.
(ert-deftest erc-scenarios-join-timing/ident ()
:tags '(:expensive-test)
(should (eq erc-autojoin-timing 'connect))
(should (= erc-autojoin-delay 30))
(let ((erc-autojoin-timing 'ident)
;; In the interest of time saving, this currently doesn't wait
;; around to verify that a second JOIN isn't sent. However, the
;; logic in `erc-autojoin--join' prevents such sending anyway,
;; meaning some other means of verification would be needed.
(erc-autojoin-delay 5))
(erc-scenarios-join-timing--services-identify-both 'ident-both)))
;;; erc-scenarios-join-timing.el ends here

View file

@ -239,7 +239,7 @@ Dialog resource directories are located by expanding the variable
process-environment)) process-environment))
(name (ert-test-name (ert-running-test))) (name (ert-test-name (ert-running-test)))
(temp-file (make-temp-file "erc-term-test-")) (temp-file (make-temp-file "erc-term-test-"))
(cmd `(let ((stats 1)) (cmd `(let (stats)
(setq enable-dir-local-variables nil) (setq enable-dir-local-variables nil)
(unwind-protect (unwind-protect
(setq stats (ert-run-tests-batch ',name)) (setq stats (ert-run-tests-batch ',name))
@ -248,7 +248,8 @@ Dialog resource directories are located by expanding the variable
(buffer-string)))) (buffer-string))))
(with-temp-file ,temp-file (with-temp-file ,temp-file
(insert buf))) (insert buf)))
(kill-emacs (ert-stats-completed-unexpected stats)))))) (kill-emacs
(if stats (ert-stats-completed-unexpected stats) 1))))))
;; The `ert-test' object in Emacs 29 has a `file-name' field. ;; The `ert-test' object in Emacs 29 has a `file-name' field.
(file-name (symbol-file name 'ert--test)) (file-name (symbol-file name 'ert--test))
(default-directory (expand-file-name (file-name-directory file-name))) (default-directory (expand-file-name (file-name-directory file-name)))

View file

@ -64,12 +64,14 @@ Assume caller intends to use `erc-display-message'."
(should (= (point) erc-input-marker))) (should (= (point) erc-input-marker)))
(defun erc-tests-common-init-server-proc (&rest args) (defun erc-tests-common-init-server-proc (&rest args)
"Create a process with `start-process' from ARGS. "Create a non-network process from ARGS with `make-process'.
Assign the result to `erc-server-process' in the current buffer." Assign the result to `erc-server-process' in the current buffer."
(setq erc-server-process (setq erc-server-process
(apply #'start-process (car args) (current-buffer) args)) (make-process :name (car args)
(set-process-query-on-exit-flag erc-server-process nil) :buffer (current-buffer)
erc-server-process) :command args
:connection-type 'pipe
:noquery t)))
;; After dropping support for Emacs 27, callers can use ;; After dropping support for Emacs 27, callers can use
;; `get-buffer-create' with INHIBIT-BUFFER-HOOKS. ;; `get-buffer-create' with INHIBIT-BUFFER-HOOKS.
@ -130,7 +132,10 @@ Use NAME for the network and the session server as well."
erc--isupport-params (make-hash-table) erc--isupport-params (make-hash-table)
erc-session-port 6667 erc-session-port 6667
erc-network (intern name) erc-network (intern name)
erc-networks--id (erc-networks--id-create name)) erc-server-current-nick "tester"
;; Derive ID from nick and network. To create a "given"
;; variant, override manually with a non-nil argument.
erc-networks--id (erc-networks--id-create nil))
(current-buffer))) (current-buffer)))
(defun erc-tests-common-string-to-propertized-parts (string) (defun erc-tests-common-string-to-propertized-parts (string)

View file

@ -0,0 +1,49 @@
;; -*- mode: lisp-data; -*-
((nick 10 "NICK tester"))
((user 10 "USER tester 0 * :tester")
(0.04 ":silver.libera.chat NOTICE * :*** Checking Ident")
(0.01 ":silver.libera.chat NOTICE * :*** Looking up your hostname...")
(0.05 ":silver.libera.chat NOTICE * :*** Found your hostname: static-23-234-108-15.test.example.com")
(0.06 ":silver.libera.chat NOTICE * :*** No Ident response")
(0.01 ":silver.libera.chat 001 tester :Welcome to the Libera.Chat Internet Relay Chat Network tester")
(0.00 ":silver.libera.chat 002 tester :Your host is silver.libera.chat[108.181.132.149/6697], running version solanum-1.0-dev")
(0.00 ":silver.libera.chat 003 tester :This server was created Wed Jul 17 2024 at 21:44:04 UTC")
(0.00 ":silver.libera.chat 004 tester silver.libera.chat solanum-1.0-dev DGIMQRSZaghilopsuwz CFILMPQRSTbcefgijklmnopqrstuvz bkloveqjfI")
(0.00 ":silver.libera.chat 005 tester ACCOUNTEXTBAN=a ETRACE FNC SAFELIST ELIST=CMNTU CALLERID=g MONITOR=100 KNOCK WHOX CHANTYPES=# EXCEPTS INVEX :are supported by this server")
(0.00 ":silver.libera.chat 005 tester CHANMODES=eIbq,k,flj,CFLMPQRSTcgimnprstuz CHANLIMIT=#:250 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=Libera.Chat STATUSMSG=@+ CASEMAPPING=rfc1459 NICKLEN=16 MAXNICKLEN=16 CHANNELLEN=50 TOPICLEN=390 :are supported by this server")
(0.01 ":silver.libera.chat 005 tester DEAF=D TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: EXTBAN=$,agjrxz :are supported by this server")
(0.00 ":silver.libera.chat 251 tester :There are 56 users and 30210 invisible on 28 servers")
(0.00 ":silver.libera.chat 252 tester 42 :IRC Operators online")
(0.00 ":silver.libera.chat 253 tester 112 :unknown connection(s)")
(0.00 ":silver.libera.chat 254 tester 22712 :channels formed")
(0.01 ":silver.libera.chat 255 tester :I have 2940 clients and 1 servers")
(0.00 ":silver.libera.chat 265 tester 2940 3488 :Current local users 2940, max 3488")
(0.00 ":silver.libera.chat 266 tester 30266 34153 :Current global users 30266, max 34153")
(0.00 ":silver.libera.chat 250 tester :Highest connection count: 3489 (3488 clients) (1113835 connections received)")
(0.01 ":silver.libera.chat 375 tester :- silver.libera.chat Message of the Day - ")
(0.00 ":silver.libera.chat 372 tester :- This server is provided by Psychz Networks.")
(0.00 ":silver.libera.chat 372 tester :- Email: support@libera.chat")
(0.00 ":silver.libera.chat 376 tester :End of /MOTD command."))
((mode 10 "MODE tester +i"))
((join 10 "JOIN #chan")
(0.00 ":tester MODE tester :+Ziw")
(0.01 ":NickServ!NickServ@services.libera.chat NOTICE tester :This nickname is registered. Please choose a different nickname, or identify via \2/msg NickServ IDENTIFY tester <password>\2"))
((privmsg 10 "PRIVMSG NickServ :IDENTIFY changeme")
(0.06 ":tester!~tester@static-23-234-108-15.test.example.com JOIN #chan"))
((mode 10 "MODE #chan")
(0.00 ":silver.libera.chat 353 tester = #chan :tester bob")
(0.01 ":silver.libera.chat 366 tester #chan :End of /NAMES list.")
(0.06 ":NickServ!NickServ@services.libera.chat NOTICE tester :You are now identified for \2tester\2.")
(0.01 ":NickServ!NickServ@services.libera.chat NOTICE tester :Last login from: \2~tester@static-23-234-108-15.test.example.com\2 on Jul 26 07:22:24 2025 +0000.")
(0.02 ":silver.libera.chat 324 tester #chan +nt")
(0.01 ":silver.libera.chat 329 tester #chan 1621432263")
(0.00 ":silver.libera.chat 900 tester tester!~tester@static-23-234-108-15.test.example.com tester :You are now logged in as tester")
(0.01 ":silver.libera.chat 396 tester user/tester :is now your hidden host (set by services.)"))
((quit 10 "QUIT :")
(0.02 ":tester!~tester@user/tester QUIT :Client Quit")
(0.02 "ERROR :Closing Link: user/tester (Client Quit)"))

View file

@ -0,0 +1,50 @@
;; -*- mode: lisp-data; -*-
((nick 10 "NICK tester"))
((user 10 "USER tester 0 * :tester")
(0.05 ":silver.libera.chat NOTICE * :*** Checking Ident")
(0.01 ":silver.libera.chat NOTICE * :*** Looking up your hostname...")
(0.04 ":silver.libera.chat NOTICE * :*** Found your hostname: static-23-234-108-15.test.example.com")
(0.04 ":silver.libera.chat NOTICE * :*** No Ident response")
(0.01 ":silver.libera.chat 001 tester :Welcome to the Libera.Chat Internet Relay Chat Network tester")
(0.00 ":silver.libera.chat 002 tester :Your host is silver.libera.chat[108.181.132.149/6697], running version solanum-1.0-dev")
(0.00 ":silver.libera.chat 003 tester :This server was created Wed Jul 17 2024 at 21:44:04 UTC")
(0.01 ":silver.libera.chat 004 tester silver.libera.chat solanum-1.0-dev DGIMQRSZaghilopsuwz CFILMPQRSTbcefgijklmnopqrstuvz bkloveqjfI")
(0.00 ":silver.libera.chat 005 tester ACCOUNTEXTBAN=a ETRACE FNC SAFELIST ELIST=CMNTU CALLERID=g MONITOR=100 KNOCK WHOX CHANTYPES=# EXCEPTS INVEX :are supported by this server")
(0.00 ":silver.libera.chat 005 tester CHANMODES=eIbq,k,flj,CFLMPQRSTcgimnprstuz CHANLIMIT=#:250 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=Libera.Chat STATUSMSG=@+ CASEMAPPING=rfc1459 NICKLEN=16 MAXNICKLEN=16 CHANNELLEN=50 TOPICLEN=390 :are supported by this server")
(0.01 ":silver.libera.chat 005 tester DEAF=D TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: EXTBAN=$,agjrxz :are supported by this server")
(0.00 ":silver.libera.chat 251 tester :There are 56 users and 30241 invisible on 28 servers")
(0.01 ":silver.libera.chat 252 tester 42 :IRC Operators online")
(0.00 ":silver.libera.chat 253 tester 109 :unknown connection(s)")
(0.01 ":silver.libera.chat 254 tester 22706 :channels formed")
(0.00 ":silver.libera.chat 255 tester :I have 2945 clients and 1 servers")
(0.00 ":silver.libera.chat 265 tester 2945 3488 :Current local users 2945, max 3488")
(0.01 ":silver.libera.chat 266 tester 30297 34153 :Current global users 30297, max 34153")
(0.00 ":silver.libera.chat 250 tester :Highest connection count: 3489 (3488 clients) (1113862 connections received)")
(0.01 ":silver.libera.chat 375 tester :- silver.libera.chat Message of the Day - ")
(0.00 ":silver.libera.chat 372 tester :- This server is provided by Psychz Networks.")
(0.01 ":silver.libera.chat 372 tester :- Email: support@libera.chat")
(0.00 ":silver.libera.chat 376 tester :End of /MOTD command."))
((mode 10 "MODE tester +i")
(0.00 ":tester MODE tester :+Ziw")
(0.01 ":NickServ!NickServ@services.libera.chat NOTICE tester :This nickname is registered. Please choose a different nickname, or identify via \2/msg NickServ IDENTIFY tester <password>\2"))
((privmsg 10 "PRIVMSG NickServ :IDENTIFY changeme")
(0.06 ":NickServ!NickServ@services.libera.chat NOTICE tester :You are now identified for \2tester\2."))
((join 10 "JOIN #chan")
(0.00 ":NickServ!NickServ@services.libera.chat NOTICE tester :Last login from: \2~tester@static-23-234-108-15.test.example.com\2 on Jul 26 07:36:19 2025 +0000.")
(0.05 ":silver.libera.chat 900 tester tester!~tester@static-23-234-108-15.test.example.com tester :You are now logged in as tester")
(0.01 ":silver.libera.chat 396 tester user/tester :is now your hidden host (set by services.)")
(0.04 ":tester!~tester@user/tester JOIN #chan"))
((mode 10 "MODE #chan")
(0.05 ":silver.libera.chat 353 tester = #chan :tester chfritsch infertux Crucial matoro sailorTheCat cavcrosby lineageovercm JulianUNDERSCORE Peetz3r Sphearion fubuki tkna fossdd germ_ ZLima12 germtest Guest8610 kedihacker sailorCat npopov gachikuku_ NightMonkey jstoker EpicNeo Peetz1r timcook jess Nick- Peetz0r polyedre Labnan Guest218797 Yorick_ Koragg MrPockets")
(0.01 ":silver.libera.chat 366 tester #chan :End of /NAMES list.")
(0.06 ":silver.libera.chat 324 tester #chan +nt")
(0.01 ":silver.libera.chat 329 tester #chan 1621432263")
(0.08 ":timcook!~timcook@malikania.fr PRIVMSG #chan :\1ACTION throws an iPad Air on tester\1"))
((quit 10 "QUIT :")
(0.01 ":tester!~tester@user/tester QUIT :Client Quit")
(0.01 "ERROR :Closing Link: user/tester (Client Quit)"))