;;; erc-scenarios-48598.el --- e2e test cases for ERC -*- lexical-binding: t -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;;
;; This file is part of GNU Emacs.
;;
;; This program 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.
;;
;; This program 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 this program. If not, see
;; .
;;; Commentary:
;;
;; These are meant to demo unwanted behavior described in bug#48598.
;; To allow for incrementally addressing those issues, they have been
;; written to *pass* when run from a historical snapshot of the tree
;; built with libraries and tools that existed at or in the months
;; leading up to 0c7a7433dce1b93a685396986d3a560c9cc291f1. See next
;; commit for updated scenarios and layout adapted for long-term use
;; with an eye toward maintenance and refactoring.
;;
;; Because 595e506c82 "Set +i by default" constitutes a breaking
;; change and these test are meant to verify pre-0c7a7433d behavior,
;; the option `erc-user-mode' has been artificially overridden to
;; restore its original default value of nil.
;;; Code:
(require 'ert-x) ; cl-lib
(eval-and-compile
(when-let ((dir (getenv "EMACS_TEST_DIRECTORY")))
(cl-pushnew (concat dir "/lisp/erc/erc-d") load-path :test #'equal)))
(require 'erc-d)
(require 'erc-d-t)
(require 'erc-backend)
(declare-function erc-network-name "erc-networks")
(defvar erc-autojoin-channels-alist)
(defvar erc-network)
(defvar erc-scenarios-resources-dir
(let ((ert-resource-directory-trim-right-regexp "\\(-48598\\)?\\.el"))
(ert-resource-directory)))
(when (boundp 'process-prioritize-lower-fds)
(setq process-prioritize-lower-fds t))
;; When interactive, teardown is already inhibited, which precludes
;; subsequent tests. So might as well treat inspection as the goal.
(unless noninteractive
(setq erc-server-auto-reconnect nil))
(defvar erc-scenarios--dialog-name nil)
(defvar erc-scenarios--extra-teardown nil)
(defun erc-scenarios-common-buflist (prefix)
"Return list of buffers with names sharing PREFIX."
(let (case-fold-search)
(delq nil (mapcar (lambda (b)
(when (string-prefix-p prefix (buffer-name b)) b))
(buffer-list)))))
(defmacro erc-scenarios-common-with-cleanup (bindings &rest body)
"Provide boilerplate cleanup tasks after calling BODY with BINDINGS.
Set `erc-scenarios-resources-dir' for the current ERT test. If a
process exists with the default dumb-server name, wait for it to start
before running BODY. If `erc-autojoin-mode' mode is bound, restore it
during cleanup if negated by BODY. Other defaults common to these test
cases are added below and can be overridden, except when wanting the
\"real\" default value, which must be looked up or captured outside of
this form."
(declare (indent 1))
(let* ((orig-autojoin-mode (make-symbol "orig-autojoin-mode"))
(get-name `(expand-file-name
(or erc-scenarios--dialog-name
(substring (symbol-name
(ert-test-name (ert-running-test)))
,(length "erc-scenarios-")))
erc-scenarios-resources-dir))
(defaults `((erc-d-u-canned-dialog-dir ,get-name)
(erc-user-mode nil)
(erc-modules (copy-sequence erc-modules))
(,orig-autojoin-mode (bound-and-true-p erc-autojoin-mode))
(erc-autojoin-channels-alist nil)
(erc-server-auto-reconnect nil))))
`(erc-d-t-with-cleanup (,@defaults ,@bindings)
(ert-info ("Restore autojoin kill ERC buffers")
(when erc-scenarios--extra-teardown
(ert-info ("Running extra teardown")
(funcall erc-scenarios--extra-teardown)))
(when (and (boundp 'erc-autojoin-mode)
(not (eq erc-autojoin-mode ,orig-autojoin-mode)))
(erc-autojoin-mode (if ,orig-autojoin-mode +1 -1)))
(when noninteractive
(when (and (boundp 'trace-buffer) (get-buffer trace-buffer))
(with-current-buffer trace-buffer
(message "%S" (buffer-string))
(kill-buffer)))
(erc-d-t-kill-related-buffers)))
(ert-info ("Wait for dumb server")
(dolist (buf (buffer-list))
(with-current-buffer buf
(when erc-d-u--process-buffer
(erc-d-t-search-for 3 "Starting")))))
(ert-info ("Activate erc-debug-irc-protocol")
(unless (and noninteractive (not erc-debug-irc-protocol))
(erc-toggle-debug-irc-protocol)))
,@body)))
(defvar erc-scenarios--port 16667)
(defun erc-scenarios--port()
"Set next port without checking if it's open."
(cl-incf erc-scenarios--port))
(defmacro erc-scenarios-with-local-watcher (found-sym target-var &rest body)
"Run BODY with mutations to TARGET-VAR recorded in FOUND-SYM."
(declare (indent 2))
(let ((func (make-symbol "func")))
`(let* (,found-sym
(,func (lambda (_s v op w)
(when (and (eq op 'set)
w ; buffer when buffer-local else nil
v)
(push v ,found-sym)))))
(should-not (get-variable-watchers ,target-var))
(add-variable-watcher ,target-var ,func)
,@body
(remove-variable-watcher ,target-var ,func)
(should-not (get-variable-watchers ,target-var)))))
;; This test lineup should match ERT's
(ert-deftest erc-scenarios-47522/ambiguous-join ()
"Recast non-bug #47522 for regression defense."
(erc-scenarios-common-with-cleanup
((erc-server-flood-penalty 0.1) ; see below
(dumb-server-foonet-buffer (get-buffer-create "*server-foonet*"))
(dumb-server-barnet-buffer (get-buffer-create "*server-barnet*"))
(dumb-server-foonet-port (erc-scenarios--port))
(dumb-server-barnet-port (erc-scenarios--port))
;; Hmm, should maybe add name as formal param to `erc-d-run'
(dumb-server-foonet (erc-d-run "localhost" dumb-server-foonet-port
"server-foonet" 'foonet))
(dumb-server-barnet (erc-d-run "localhost" dumb-server-barnet-port
"server-barnet" 'barnet))
(expect (erc-d-t-make-expecter))
erc-server-buffer-foo
erc-server-buffer-bar)
(ert-info ("Connect to foonet")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port dumb-server-foonet-port
:nick "tester"
:password "changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(funcall expect 3 "debug mode")
(erc-cmd-JOIN "#chan")))
(erc-d-t-wait-for 2 "Buffer #chan@foonet exists"
(get-buffer "#chan"))
(ert-info ("Connect to barnet")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port dumb-server-barnet-port
:nick "tester"
:password "changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(funcall expect 1 "debug mode")))
;; If either of these two went through, we'd get a bad match on
;; exchange "linger" (right?)
(ert-info ("Buffers don't exist")
(with-current-buffer erc-server-buffer-foo
(erc-cmd-JOIN "#chan"))
(sit-for 0.1)
(with-current-buffer "#chan"
(erc-cmd-JOIN "#chan"))
(sit-for 0.1)
(erc-d-t-wait-for 2 "Buffer #chan@foonet not replaced"
(get-buffer "#chan"))
(erc-d-t-wait-for 1 "Buffer #chan@barnet does not exist"
(= 1 (length (erc-scenarios-common-buflist "#chan"))))
;; Still respects chine2e wall because subproc would dump same
;; to stdout
(with-current-buffer dumb-server-barnet-buffer
(goto-char (point-min))
(should-not (search-forward "JOIN" nil t))))
(ert-info ("All #chan@foonet output consumed")
(with-current-buffer "#chan" ; <- First chan joined (foonet)
(funcall expect 3 "welcome!")
(while (accept-process-output erc-server-process))
(funcall expect 3 "husband")))))
(ert-deftest erc-scenarios-47522/foil-in-server-buf ()
"Different spin on non-bug #47522 for regression defense."
(erc-scenarios-common-with-cleanup
((erc-d-linger-secs 0.5)
(erc-server-flood-penalty 0.1)
(dumb-server-foonet-buffer (get-buffer-create "*server-foonet*"))
(dumb-server-barnet-buffer (get-buffer-create "*server-barnet*"))
(dumb-server-foonet-port (erc-scenarios--port))
(dumb-server-barnet-port (erc-scenarios--port))
(dumb-server-foonet (erc-d-run "localhost" dumb-server-foonet-port
"server-foonet" 'foonet))
(dumb-server-barnet (erc-d-run "localhost" dumb-server-barnet-port
"server-barnet" 'barnet))
(expect (erc-d-t-make-expecter))
erc-server-buffer-foo
erc-server-buffer-bar)
(ert-info ("Connect to foonet")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port dumb-server-foonet-port
:nick "tester"
:password "changeme"
:full-name "tester")))
(with-current-buffer erc-server-buffer-foo
(funcall expect 3 "debug mode")
(erc-cmd-JOIN "#chan"))
(erc-d-t-wait-for 2 "Buffer #chan@foonet exists"
(get-buffer "#chan"))
(ert-info ("Connect to barnet")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port dumb-server-barnet-port
:nick "tester"
:password "changeme"
:full-name "tester")))
(with-current-buffer erc-server-buffer-bar
(funcall expect 1 "debug mode")
(erc-cmd-JOIN "#chan"))
(erc-d-t-wait-for 3 "Buffer #chan@barnet exists"
(get-buffer "#chan/127.0.0.1<2>"))
(erc-d-t-wait-for 2 "Buffer #chan@foonet replaced"
(and (get-buffer "#chan/127.0.0.1")
(not (get-buffer "#chan"))))
(ert-info ("All #chan@foonet output consumed")
(with-current-buffer "#chan/127.0.0.1" ; <- First chan joined (foonet)
(funcall expect 3 "bob")
(funcall expect 3 "was created on")
(while (accept-process-output erc-server-process))
(funcall expect 3 "prosperous")))
(ert-info ("All #chan@barnet output consumed")
(with-current-buffer "#chan/127.0.0.1<2>"
(funcall expect 3 "mike")
(funcall expect 3 "was created on")
(while (accept-process-output erc-server-process))
(funcall expect 3 "ingenuous")))))
;; On some systems, this first bunch may need some bumping of timeouts,
;; linger-secs, etc. See function `erc-d-u-rewrite-for-slow-mo'.
;; XXX 9bb8d90cdd Allow irc network symbols in erc-autojoin-channels-alist
;; Fixes the inciting action here but not the root cause
(ert-deftest erc-scenarios-48598/clash-of-chans/autojoin ()
(ert-skip "obsolete")
(should erc-reuse-buffers)
(erc-scenarios-common-with-cleanup
((erc-d-u-with-cleanup-sleep-secs 1)
(erc-server-flood-penalty 0.5)
(port (erc-scenarios--port))
(dumb-server (erc-d-run "localhost" port
'foonet 'barnet 'foonet-again))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
(expect (erc-d-t-make-expecter))
erc-server-buffer-foo erc-server-process-foo
erc-server-buffer-bar erc-server-process-bar)
(should (memq 'autojoin erc-modules))
(ert-info ("Connect to foonet")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(setq erc-server-process-foo erc-server-process)
(should (string= (buffer-name) (format "127.0.0.1:%d" port)))
(erc-d-t-wait-for 1 "foonet Network detected"
(string= (erc-network-name) "foonet"))
(funcall expect 5 "foonet")))
(ert-info ("Join #chan, then quit")
(with-current-buffer erc-server-buffer-foo
(erc-cmd-JOIN "#chan"))
(erc-d-t-wait-for 5 "Buffer #chan exists"
(get-buffer "#chan"))
(with-current-buffer "#chan"
(funcall expect 5 "vile thing")
(erc-cmd-QUIT "")))
(erc-d-t-wait-for 2 "foo death"
(not (process-live-p erc-server-process-foo)))
(should (equal erc-autojoin-channels-alist '(("foonet.org" "#chan"))))
(ert-info ("Connect to barnet")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "barnet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(setq erc-server-process-bar erc-server-process)
(should (string= (buffer-name) (format "127.0.0.1:%d" port)))
(erc-d-t-wait-for 5 "barnet Network detected"
(should-not (eq (process-status erc-server-process) 'failed))
(eq erc-network 'barnet))))
(ert-info ("Server buffers are the same")
(should (eq erc-server-buffer-foo erc-server-buffer-bar))
(should-not (cdr (erc-scenarios-common-buflist "127.0.0.1"))))
(ert-info ("Only one #chan buffer exists")
(should (= 1 (length (erc-scenarios-common-buflist "#chan")))))
(ert-info ("#chan is auto-joined, output exclusive to barnet")
(with-current-buffer "#chan"
(funcall expect 2 "")
(erc-d-t-wait-for 3 "server-buffer is barnet"
(eq erc-server-process erc-server-process-bar))))
(ert-info ("Reconnect to foonet")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(setq erc-server-process-foo erc-server-process)
(erc-d-t-wait-for 5 "foonet buffer renamed"
(string= (buffer-name)
(format "127.0.0.1:%d/127.0.0.1<2>" port)))
(erc-d-t-wait-for 2 "foonet Network detected"
(eq erc-network 'foonet))
(funcall expect 5 "foonet")))
(ert-info ("#chan's server alternates as does its content")
(with-current-buffer "#chan"
(erc-scenarios-with-local-watcher procs 'erc-server-process
(erc-d-t-wait-for 3 "server buffer alternates"
(and (memq erc-server-process-foo procs)
(memq erc-server-process-bar procs))))
(funcall expect 2 "")
(funcall expect 2 "")))
(ert-info ("All output received")
(with-current-buffer "#chan"
(while (accept-process-output erc-server-process-foo))
(while (accept-process-output erc-server-process-bar))
;; Ordering here may not be predictable
(erc-d-t-search-for 1 "not given me")
(erc-d-t-search-for 1 "hath an uncle here")))
(erc-d-t-wait-for 5 "dumb-server death"
(not (eq (process-status dumb-server) 'run)))))
(ert-deftest erc-scenarios-48598/clash-of-chans/bouncer-history ()
(should erc-reuse-buffers)
(erc-scenarios-common-with-cleanup
((erc-d-u-with-cleanup-sleep-secs 1)
(erc-d-linger-secs 1)
(port (erc-scenarios--port))
(dumb-server (erc-d-run "localhost" port 'foonet 'barnet))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
(erc-server-flood-penalty 0.5)
(expect (erc-d-t-make-expecter))
erc-autojoin-channels-alist
erc-server-buffer-foo erc-server-process-foo
erc-server-buffer-bar erc-server-process-bar)
(ert-info ("Connect to foonet")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(setq erc-server-process-foo erc-server-process)
(should (string= (buffer-name) (format "127.0.0.1:%d" port)))
(funcall expect 5 "foonet")))
(ert-info ("Connect to barnet")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "barnet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(setq erc-server-process-bar erc-server-process)
;; Prior to 88567ca8 "Fix ERC Reuse buffer behavior", this didn't
;; feature an suffix
(erc-d-t-wait-for 5 "overshot n-suffixed redundant name"
(string= (buffer-name) (format "127.0.0.1:%d/127.0.0.1<2>" port)))
(funcall expect 5 "barnet")))
(ert-info ("Server buffers are unique")
(should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
(should (= 2 (length (erc-scenarios-common-buflist "127.0.0.1")))))
(ert-info ("Networks named correctly")
(with-current-buffer erc-server-buffer-foo
(erc-d-t-wait-for 3 "network name foonet"
(string= (erc-network-name) "foonet")))
(with-current-buffer erc-server-buffer-bar
(erc-d-t-wait-for 3 "network name barnet"
(string= (erc-network-name) "barnet"))))
(ert-info ("Only one #chan buffer exists")
(should (= 1 (length (erc-scenarios-common-buflist "#chan")))))
(ert-info ("#chan's server alternates as does its content")
(erc-scenarios-with-local-watcher procs 'erc-server-process
(with-current-buffer "#chan"
(erc-d-t-search-for 1 "")
(erc-d-t-search-for 1 "")
(erc-d-t-wait-for 3 "server buffer alternates"
(and (memq erc-server-process-foo procs)
(memq erc-server-process-bar procs))))))
(ert-info ("All output sent")
(with-current-buffer "#chan"
(while (accept-process-output erc-server-process-foo))
(while (accept-process-output erc-server-process-bar))
(erc-d-t-search-for 3 "please your lordship")))
(erc-d-t-wait-for 5 "dumb-server dies on its own"
(not (eq (process-status dumb-server) 'run)))))
(ert-deftest erc-scenarios-48598/clash-of-chans/rename-buffers ()
(should erc-reuse-buffers)
(erc-scenarios-common-with-cleanup
((erc-d-u-with-cleanup-sleep-secs 1)
(erc-server-flood-penalty 0.1)
(port (erc-scenarios--port))
(dumb-server (erc-d-run "localhost" port 'foonet 'barnet))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
(expect (erc-d-t-make-expecter))
(erc-rename-buffers t)
erc-server-buffer-foo erc-server-process-foo
erc-server-buffer-bar erc-server-process-bar)
(ert-info ("Connect to foonet, server briefly named nil")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(setq erc-server-process-foo erc-server-process)
(should (string= (buffer-name) "nil"))
(erc-d-t-wait-for 3 "network name foonet"
(string= (erc-network-name) "foonet"))
(funcall expect 5 "foonet")))
(erc-d-t-wait-for 5 "Foonet's server buffer renamed"
(get-buffer "foonet"))
(should (eq erc-server-buffer-foo (get-buffer "foonet")))
(ert-info ("Join #chan@foonet")
(with-current-buffer erc-server-buffer-foo
(erc-cmd-JOIN "#chan"))
(erc-d-t-wait-for 5 "Buffer #chan created"
(get-buffer "#chan"))
(with-current-buffer "#chan"
(funcall expect 5 "")))
(ert-info ("Connect to barnet, server briefly named nil")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "barnet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(setq erc-server-process-bar erc-server-process)
(should (string= (buffer-name) "nil"))
(erc-d-t-wait-for 3 "network name barnet"
(string= (erc-network-name) "barnet"))
(funcall expect 5 "barnet")))
(erc-d-t-wait-for 5 "Barnet's server buffer renamed"
(get-buffer "barnet"))
(should (eq erc-server-buffer-bar (get-buffer "barnet")))
(ert-info ("Server buffers are unique, no buffer with old names")
(should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
(should-not (erc-scenarios-common-buflist "127.0.0.1")))
(ert-info ("Join #chan@barnet")
(with-current-buffer erc-server-buffer-bar
(erc-cmd-JOIN "#chan")))
(ert-info ("#chan's server alternates along with content")
(erc-scenarios-with-local-watcher procs 'erc-server-process
(with-current-buffer "#chan"
(erc-d-t-search-for 1 "")
(erc-d-t-search-for 1 "")
(erc-d-t-wait-for 3 "server buffer alternates"
(and (memq erc-server-process-foo procs)
(memq erc-server-process-bar procs))))))
(ert-info ("Only one #chan buffer exists")
(should (= 1 (length (erc-scenarios-common-buflist "#chan")))))
(ert-info ("All output sent")
(with-current-buffer "#chan"
(while (accept-process-output erc-server-process-foo))
(while (accept-process-output erc-server-process-bar))
(erc-d-t-search-for 1 "ape is dead")
(erc-d-t-search-for 1 "keeps you from dishonour")))
(erc-d-t-wait-for 5 "dumb-server death"
(not (eq (process-status dumb-server) 'run)))))
;; This one is a temporary departure from the "assume defaults" rule
;; mentioned in the Commentary. (Which is bad.)
;;
;; TODO tag this as :unstable if ever adding to Emacs
;; TODO see if meaning is preserved when autojoin is ON (if so, adapt)
(ert-deftest erc-scenarios-48598/clash-of-chans/uniquify-fail ()
(should erc-reuse-buffers)
(erc-scenarios-common-with-cleanup
((erc-d-u-with-cleanup-sleep-secs 1)
(port (erc-scenarios--port))
(dumb-server (erc-d-run "localhost" port 'foonet 'barnet))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
(expect (erc-d-t-make-expecter))
(erc-server-flood-penalty 0.1) ; hack
(erc-modules (remq 'autojoin erc-modules))
erc-server-buffer-foo erc-server-process-foo
erc-server-buffer-bar erc-server-process-bar
erc-autojoin-channels-alist
erc-reuse-buffers)
(when (bound-and-true-p erc-autojoin-mode)
(erc-autojoin-mode -1))
(ert-info ("Connect to foonet")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(setq erc-server-process-foo erc-server-process)
;; Compare suffixed name here to that in bouncer-history variant
;; (which has `erc-reuse-buffers' set to the default value of t)
(should (string= (buffer-name)
(format "127.0.0.1:%d/127.0.0.1" port)))
(funcall expect 5 "foonet")))
(ert-info ("Connect to barnet")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "barnet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(setq erc-server-process-bar erc-server-process)
;; Prior to 88567ca8 "Fix ERC Reuse buffer behavior", this didn't
;; feature an suffix
(erc-d-t-wait-for 5 "overshot n-suffixed redundant name"
(string= (buffer-name) (format "127.0.0.1:%d/127.0.0.1<2>" port)))
(funcall expect 5 "barnet")))
(ert-info ("Server buffers are unique")
(should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
(should (= 2 (length (erc-scenarios-common-buflist "127.0.0.1")))))
(ert-info ("Networks named correctly")
(with-current-buffer erc-server-buffer-foo
(erc-d-t-wait-for 3 "network name foonet"
(string= (erc-network-name) "foonet")))
(with-current-buffer erc-server-buffer-bar
(erc-d-t-wait-for 3 "network name barnet"
(string= (erc-network-name) "barnet"))))
(ert-info ("Only one #chan buffer exists")
(let ((chan-bufs (erc-scenarios-common-buflist "#chan")))
(should (string= (buffer-name (pop chan-bufs)) "#chan/127.0.0.1"))
(should-not chan-bufs)))
;; From here on diverges from "48598/clash-of-chans/bouncer-history"
(ert-info ("#chan's server alternates along with its content")
(with-current-buffer "#chan/127.0.0.1"
(erc-scenarios-with-local-watcher procs 'erc-server-process
(erc-d-t-wait-for 3 "server buffer alternates"
(and (memq erc-server-process-foo procs)
(memq erc-server-process-bar procs))))
;; XXX this only works if the REPLY to this PART is received
;; when foonet is dominant, which is out of our control.
(with-current-buffer erc-server-buffer-foo
(erc-cmd-PART "#chan"))
;; Remaining foonet output is displayed but barnet is cut off
(erc-d-t-search-for 1 "shake my sword")))
(ert-info ("Somehow #chan@barnet is created")
(erc-d-t-wait-for 5 "#chan@barnet"
(get-buffer "#chan/127.0.0.1<2>"))
(with-current-buffer "#chan/127.0.0.1<2>"
(should (eq erc-server-process erc-server-process-bar))))
(ert-info ("Rejoin #chan@foonet")
(with-current-buffer "#chan/127.0.0.1"
(funcall expect 3 "You have left channel #chan")
(erc-cmd-JOIN "#chan")
(funcall expect 3 "You have joined channel #chan")
(funcall expect 3 "#chan was created on")
(funcall expect 3 "")
(should (eq erc-server-process erc-server-process-foo))
(erc-d-t-search-for -0.2 "" (point))))
(ert-info ("Part chan@barnet")
(with-current-buffer "#chan/127.0.0.1<2>"
(let ((previous-end (point-max)))
(goto-char previous-end)
(should-not (search-forward "alice" nil t)))
(funcall expect 3 "Arm it in rags")
(erc-cmd-PART "#chan")
(funcall expect 3 "You have left channel #chan")
(erc-cmd-JOIN "#chan")))
(should (= 2 (length (erc-scenarios-common-buflist "#chan"))))
(ert-info ("#chan output alternates as before")
(with-current-buffer "#chan/127.0.0.1"
(funcall expect 3 "You have joined channel #chan")
(funcall expect 1 "Users on #chan: @mike joe tester")
(funcall expect 5 "") ; bob appears after ^
(ert-info ("All output sent")
(while (accept-process-output erc-server-process-bar))
(funcall expect 10 "soul black"))))
(while (accept-process-output erc-server-process-foo))
(erc-d-t-wait-for 5 "dumb-server dies on its own"
(not (eq (process-status dumb-server) 'run)))))
;; This one also disables autojoin (see comment for "uniquify-fail")
(ert-deftest erc-scenarios-48598/clash-of-chans/uniquify-litter ()
(should erc-reuse-buffers)
(erc-scenarios-common-with-cleanup
((erc-d-u-with-cleanup-sleep-secs 1)
(expect (erc-d-t-make-expecter))
(port (erc-scenarios--port))
(dumb-server (erc-d-run "localhost" port 'foonet 'barnet))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
(erc-server-flood-penalty 0.5)
(erc-modules (remq 'autojoin erc-modules))
erc-reuse-buffers
erc-server-buffer-foo erc-server-process-foo
erc-server-buffer-bar erc-server-process-bar)
(when (bound-and-true-p erc-autojoin-mode)
(erc-autojoin-mode -1))
(ert-info ("Connect to foonet, get uniquified buffer name")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(setq erc-server-process-foo erc-server-process)
;; Compare suffixed name here to that in bouncer-history variant
;; (which has `erc-reuse-buffers' set to the default value of (t))
(should (string= (buffer-name)
(format "127.0.0.1:%d/127.0.0.1" port)))
(erc-d-t-search-for 5 "foonet")))
(ert-info ("Connect to barnet, get uniquified buffer name")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "barnet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(setq erc-server-process-bar erc-server-process)
;; Prior to 88567ca8 "Fix ERC Reuse buffer behavior", this didn't
;; feature an suffix
(erc-d-t-wait-for 5 "overshot n-suffixed redundant name"
(string= (buffer-name) (format "127.0.0.1:%d/127.0.0.1<2>" port)))
(funcall expect 5 "barnet")))
(ert-info ("Server buffers are unique")
(should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
(should (= 2 (length (erc-scenarios-common-buflist "127.0.0.1")))))
(ert-info ("Networks named correctly")
(with-current-buffer erc-server-buffer-foo
(erc-d-t-wait-for 3 "network name foonet"
(string= (erc-network-name) "foonet")))
(with-current-buffer erc-server-buffer-bar
(erc-d-t-wait-for 3 "network name barnet"
(string= (erc-network-name) "barnet"))))
(ert-info ("Only one #chan buffer exists")
(let ((chan-bufs (erc-scenarios-common-buflist "#chan")))
(should (string= (buffer-name (pop chan-bufs)) "#chan/127.0.0.1"))
(should-not chan-bufs)))
;; From here on diverts from other "clash-of-chans"
(ert-info ("#chan's server alternates as does its content")
(with-current-buffer "#chan/127.0.0.1"
(erc-scenarios-with-local-watcher procs 'erc-server-process
(erc-d-t-wait-for 3 "server buffer alternates"
(and (memq erc-server-process-foo procs)
(memq erc-server-process-bar procs))))
(with-current-buffer erc-server-buffer-foo
(erc-cmd-PART "#chan"))
(funcall expect 1 "")))
(ert-info ("Somehow #chan@barnet is created")
(erc-d-t-wait-for 4 "#chan@barnet"
(get-buffer "#chan/127.0.0.1<2>")))
(ert-info ("Part chan@barnet and rejoin")
(with-current-buffer "#chan/127.0.0.1<2>"
(should (eq erc-server-process erc-server-process-bar))
(funcall expect 3 "Claudio as himself")
(let ((previous-end (point-max)))
(goto-char previous-end)
(should-not (search-forward "alice" nil t)))
(erc-cmd-PART "#chan")
(funcall expect 3 "You have left channel #chan")
(erc-cmd-JOIN "#chan")))
(ert-info ("New #chan@barnet is created")
(erc-d-t-wait-for 3 "#chan@barnet"
(get-buffer "#chan/127.0.0.1<3>"))
(with-current-buffer "#chan/127.0.0.1<3>"
(should (eq erc-server-process erc-server-process-bar))
(funcall expect 3 "You have joined channel #chan")))
(ert-info ("Rejoin #chan@foonet")
(with-current-buffer "#chan/127.0.0.1"
(funcall expect 3 "You have left channel #chan")
(erc-cmd-JOIN "#chan")
(funcall expect 3 "You have joined channel #chan")
(funcall expect 3 "#chan was created on")
(let ((pos (funcall expect 3 "")))
(should (eq erc-server-process erc-server-process-foo))
(erc-d-t-wait-for -0.2 "exclusive to foonet"
(goto-char pos)
(search-forward "joe" nil t)))))
(should (= 3 (length (erc-scenarios-common-buflist "#chan"))))
(should (= 2 (length (erc-scenarios-common-buflist "127.0.0.1"))))
(while (accept-process-output erc-server-process-foo))
(while (accept-process-output erc-server-process-bar))
(with-current-buffer "#chan/127.0.0.1"
(funcall expect 3 "Phebe's cruelty"))
(with-current-buffer "#chan/127.0.0.1<3>"
(funcall expect 3 "world-without-end"))
(erc-d-t-wait-for 5 "dumb-server dies on its own"
(not (eq (process-status dumb-server) 'run)))))
(ert-deftest erc-scenarios-48598/rebuffed/foil-rename ()
(erc-scenarios-common-with-cleanup
((erc-d-linger-secs 0.5)
(port (erc-scenarios--port))
;; Again, like "gapless" above, barnet is loaded first because
;; that's what ERC requests despite the invocation order
(dumb-server (erc-d-run "localhost" port 'barnet 'foonet))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
(erc-rename-buffers t)
erc-autojoin-channels-alist
erc-server-buffer-foo
erc-server-buffer-bar)
(ert-info ("Connect to foonet, buffer initially named nil")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(should (string= (buffer-name) "nil"))))
(ert-info ("Connect to barnet")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "barnet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
(erc-d-t-wait-for 1 "server for foonet renamed" (get-buffer "foonet"))
(erc-d-t-wait-for 1 "server for barnet renamed" (get-buffer "barnet"))
(ert-info ("Server buffers are unique and temp names are absent")
(should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
(should-not (erc-scenarios-common-buflist "127.0.0.1"))
(should-not (get-buffer "nil")))
(ert-info ("Channel buffers are both healthy")
(with-current-buffer "#foo"
(while (accept-process-output erc-server-process))
(erc-d-t-search-for 1 "whence you are")
(delete-process erc-server-process))
(with-current-buffer "#bar"
(while (accept-process-output erc-server-process))
(erc-d-t-search-for 1 "his second fit")
(delete-process erc-server-process)))
(erc-d-t-wait-for 5 "dumb-server dies naturally"
(not (process-live-p dumb-server)))))
;; Note: when inspecting this one interactively, sometimes server buffers
;; appear as "nil" if the disconnect hook ran before the latest mode-line
;; update.
(ert-deftest erc-scenarios-48598/rebuffed/gapless ()
;; This is stable with deterministic ordering before and just after
;; 0c7a7433dce1b93a685396986d3a560c9cc291f1
;; Problem remains but would require fancier footwork to show (basically
;; pattern matching and hot loading one of two dialogs)
:tags '(:unstable)
(ert-skip "obsolete")
(erc-scenarios-common-with-cleanup
((erc-d-linger-secs 2)
(erc-server-flood-penalty erc-server-flood-penalty)
;; Barnet is loaded first because that's what's requested first by
;; the client, as shown below.
(dumb-server (erc-d-run "localhost" (erc-scenarios--port)
'barnet 'foonet))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
(expect (erc-d-t-make-expecter))
erc-autojoin-channels-alist
erc-server-buffer-foo erc-server-process-foo
erc-server-buffer-bar erc-server-process-bar
timeout-sentinel)
(ert-info ("Connect twice to same endpoint without pausing")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port erc-scenarios--port
:nick "tester"
:password "foonet:changeme"
:full-name "tester")
erc-server-buffer-bar (erc :server "127.0.0.1"
:port erc-scenarios--port
:nick "tester"
:password "barnet:changeme"
:full-name "tester")))
(ert-info ("Returned server buffers are identical")
(should (eq erc-server-buffer-foo erc-server-buffer-bar)))
(ert-info ("Both connections actually happen")
(should (get-process "erc-d-server"))
(let ((name (format "erc-127.0.0.1-%d" erc-scenarios--port)))
(setq erc-server-process-foo (get-process name)
erc-server-process-bar (get-process (concat name "<1>")))))
(set-process-query-on-exit-flag erc-server-process-foo nil)
(set-process-query-on-exit-flag erc-server-process-bar nil)
(with-current-buffer erc-server-buffer-bar
(funcall expect 1 "marked as being away"))
(cl-letf (((symbol-function 'erc-d--expire)
(lambda (_ e) (push e timeout-sentinel))))
(erc-d-t-wait-for 20 "Buffer #bar exists"
(get-buffer "#bar"))
(with-current-buffer erc-server-buffer-bar
;; XXX a cheat to save some time. Verify by commenting out
;; and bumping timeouts. Should still pass (after ~10 secs).
(ert-info ("Kludge to save some time")
(setq erc-server-flood-penalty 0)
(erc-server-send-queue erc-server-buffer-bar))
(erc-d-t-wait-for 5 "all messages actually sent"
(not erc-server-flood-queue)))
(with-current-buffer "#bar"
(erc-d-t-search-for 5 "Unauthorized command")
(erc-d-t-search-for 5 "was created on"))
(erc-d-t-wait-for 2 "#foo dialog times out" timeout-sentinel)
(let ((e (pop timeout-sentinel)))
(should-not timeout-sentinel)
(should (eq 'pass (erc-d-exchange-tag e)))
(should (string= "\\`PASS doa" (erc-d-exchange-pattern e)))))
(while (accept-process-output erc-server-process-foo))
(while (accept-process-output erc-server-process-bar))
(erc-d-t-wait-for 5 "dumb-server to die on its own"
(not (process-live-p dumb-server)))))
(defun erc-scenarios-common--48598/rebuffed/reuseless ()
(erc-scenarios-common-with-cleanup
((erc-d-linger-secs 1)
(port (erc-scenarios--port))
(dumb-server (erc-d-run "localhost" port 'foonet 'barnet))
(dumb-server-buffer (get-buffer "*erc-d-server*"))
erc-autojoin-channels-alist
erc-server-buffer-foo
erc-server-buffer-bar)
(ert-info ("Connect to foonet")
(setq erc-server-buffer-foo (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "foonet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-foo
(should (string= (buffer-name)
(if erc-reuse-buffers
(format "127.0.0.1:%d" port)
(format "127.0.0.1:%d/127.0.0.1" port))))
(erc-d-t-search-for 1 "marked as being away")))
(ert-info ("Connect to barnet")
(setq erc-server-buffer-bar (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "barnet:changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer-bar
(should (string= (buffer-name)
(if erc-reuse-buffers
(format "127.0.0.1:%d/127.0.0.1<2>" port)
(format "127.0.0.1:%d/127.0.0.1" port))))
(erc-d-t-search-for 1 "marked as being away")))
(ert-info ("Server buffers are unique with option, identical without")
(if erc-reuse-buffers
(should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
(should (eq erc-server-buffer-foo erc-server-buffer-bar))))
(ert-info ("When the option is disabled, only one buffer survives")
(should (= (length (erc-scenarios-common-buflist "127.0.0.1"))
(if erc-reuse-buffers 2 1))))
;; Sometimes we get an EOF, but it's rare
(erc-d-t-wait-for 5 "Let dumb server die on its own"
(not (process-live-p dumb-server)))))
(ert-deftest erc-scenarios-48598/rebuffed/reuseless--enabled ()
(should erc-reuse-buffers)
(let ((erc-scenarios--dialog-name "48598/rebuffed/reuseless"))
(erc-scenarios-common--48598/rebuffed/reuseless)))
(ert-deftest erc-scenarios-48598/rebuffed/reuseless--disabled ()
(should erc-reuse-buffers)
(let ((erc-scenarios--dialog-name "48598/rebuffed/reuseless")
erc-reuse-buffers)
(erc-scenarios-common--48598/rebuffed/reuseless)))
;;; erc-scenarios-48598.el ends here