emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

fix/bug-48598 5cbc7e1bb7: [CATCH-UP] Synchronize with patch set on track


From: F. Jason Park
Subject: fix/bug-48598 5cbc7e1bb7: [CATCH-UP] Synchronize with patch set on tracker
Date: Wed, 25 May 2022 08:11:58 -0400 (EDT)

branch: fix/bug-48598
commit 5cbc7e1bb7d7525d10547bafc51ddc6b798dbc81
Author: F. Jason Park <jp@neverwas.me>
Commit: F. Jason Park <jp@neverwas.me>

    [CATCH-UP] Synchronize with patch set on tracker
    
    Also includes relevant parts of these commits from master:
    
    commit 4d60dcd87c72f7b96cac04233ffb253bd79f5bb1
    Author: F. Jason Park <jp@neverwas.me>
    Date:   Tue May 24 06:41:40 2022 -0700
    
        ; Fix inevitable failure in erc-dcc test
    
        * test/lisp/erc/erc-dcc-tests.el
        (erc-dcc-tests--dcc-handle-ctcp-send): Shadow hook to prevent the
        erc-button module from interfering with tests that use this helper.
    
    commit 5f6e64a316adc6cd6ea140453718160643c6cd93
    Author: Lars Ingebrigtsen <larsi@gnus.org>
    Date:   Tue May 24 19:02:06 2022 +0200
    
        Don't use `format-message' to make doc strings in erc-backend
    
        * lisp/erc/erc-backend.el (define-erc-response-handler): Use
        `format' here instead of `format-message' since Emacs will expand
        the doc string later when the user asks for help about the symbols
        defined.
    
    commit 73b40d8716f6416f75689d3e2442f365130f45b9
    Author: F. Jason Park <jp@neverwas.me>
    Date:   Thu May 5 21:13:47 2022 -0700
    
        Recognize DCC SSEND when receiving files in erc-dcc
    
        * lips/erc/erc-dcc.el (erc-dcc-open-network-stream): Use TLS
        for new connections when :secure flag is set.
        (erc-dcc-do-GET-command): Set secure flag when user explicitly passes
        an "-s" option.
        (erc-dcc-do-LIST-command): Show an "s" to indicate a secure connection
        when applicable.
        (erc-dcc-query-handler-alist): Add extra items for "SSEND", etc.
        (erc-dcc-handle-ctcp-send): Set secure flag when a leading "S" appears
        in the command type.
    
    commit df1e553688be6e97198e293a1042a1bbbce98271
    Author: F. Jason Park <jp@neverwas.me>
    Date:   Sat Apr 30 02:16:46 2022 -0700
    
        Accommodate nonstandard turbo file senders in erc-dcc
    
        * lisp/erc/erc-dcc.el (erc-dcc-list): Document optional :turbo item.
        (erc-message-english-dcc-list-{head,line,item}): Adjust format strings
        to make room for "(T)" turbo indicator.
        (erc-dcc-do-GET-command): Optionally set :turbo in `erc-dcc-list'
        entry when passed "-t" in the "/DCC GET" slash command.  Also add
        switch to command line in front-matter Commentary, but refrain from
        publicizing further because our implementation is only defensive and
        only for receiving.
        (erc-dcc-do-LIST): Print message with new format specifier for turbo
        status.
        (erc-dcc-ctcp-query-send-regexp): Account for T- and S-prefixed
        commands.  Receiving from an SSEND-capable sender will be added in a
        subsequent commit.
        (erc-dcc-handle-ctcp-send): Set :turbo item in `erc-dcc-list' member
        when new match group is nonempty.
        (erc-dcc--X-send-final-turbo-ack): New internal variable and potential
        future option for extreme corner cases involving maverick turbo
        senders, like WeeChat, who don't use the TSEND command variant.
        (erc-dcc-get-filter): Don't send when turbo is active.
    
        * test/lisp/erc/erc-dcc-tests.el: Add new file.
        (Bug#54458)
    
    commit 758775f15849a5c6f700ab7111449c3ec678bd8a
    Author: F. Jason Park <jp@neverwas.me>
    Date:   Sat Apr 9 23:32:22 2022 -0700
    
        Allow matching against string values in erc-dcc-member
    
        * lisp/erc/erc-dcc.el (erc-dcc-member): Be more tolerant in the
        catch-all case by testing for equality instead of identity.
        (erc-dcc-do-GET-command): Pass file name when querying
        `erc-dcc-member'. (Bug#54458)
    
    commit 37e26fc5379715010297adff5109736b7ede5cd7
    Author: F. Jason Park <jp@neverwas.me>
    Date:   Mon Mar 28 02:24:43 2022 -0700
    
        Don't send reports in erc-dcc-get-filter when nested
    
        * lisp/erc/erc-dcc.el (erc-dcc-get-filter): Don't bother sending a
        "received so far" receipt if another attempt is still ongoing.
        (Bug#54458)
    
    commit 93f9f6866aea3b0fd09e6c0c7964b265fd086d00
    Author: F. Jason Park <jp@neverwas.me>
    Date:   Wed Mar 30 17:16:11 2022 -0700
    
        Summarize failed transfers in erc-dcc
    
        * lisp/erc/erc-dcc.el (erc-dcc-get-sentinel): Display error when total
        byte count received is lower than expected.
        (erc-message-english-dcc-get-failed): Add `dcc-get-failed' to the
        English messages catalog.
        (erc-dcc-get-file): Tweak initialization of `erc-dcc-entry-data'.
        (Bug#54458)
    
    commit cfd7edb5a9048324ab2a714365ef155efd918cb0
    Author: समीर सिंह Sameer Singh <lumarzeli30@gmail.com>
    Date:   Thu May 19 01:08:14 2022 +0530
    
        Rename Oriya to Odia, and more
    
        Due to "The Orissa (Alteration of Name) Act, 2011"
        (https://legislative.gov.in/sites/default/files/A2011-15.pdf)
        Oriya has been renamed to Odia.
    
        * lisp/language/indian.el (set-language-info-alist): Rename
        Oriya to Odia.  Improve Oriya composition rules.
        * lisp/leim/quail/indian.el ("odia"): New input method.
        * lisp/erc/erc-lang.el: Obsolete the iso-638-languages variable
        (which was a typo) and replace it with iso-639-1-languages.
    
        * etc/HELLO: Rename Oriya to Odia.
        Replace the old Odia greeting with the new one.
        Add a Hindi greeting separate from the Devanagari one.
        (Bug#55493)
    
    commit 942bc9c8f56c072c4c76e010370b84ae9adc3366
    Author: Stefan Kangas <stefan@marxist.se>
    Date:   Fri May 13 22:29:53 2022 +0200
    
        Don't use obsolete yow library
    
        * lisp/erc/erc.el (erc-quit-reason-zippy, erc-part-reason-zippy):
        Don't use obsolete yow library.
---
 doc/misc/erc.texi                                  | 224 ++++-----
 etc/ERC-NEWS                                       |  86 ++++
 lisp/erc/erc-backend.el                            |  25 +-
 lisp/erc/erc-button.el                             |  20 +-
 lisp/erc/erc-dcc.el                                | 137 +++--
 lisp/erc/erc-join.el                               |  56 ++-
 lisp/erc/erc-lang.el                               |  12 +-
 lisp/erc/erc-networks.el                           | 274 +++++-----
 lisp/erc/erc-services.el                           |  28 +-
 lisp/erc/erc.el                                    | 162 +++---
 test/lisp/erc/erc-dcc-tests.el                     | 167 ++++++
 test/lisp/erc/erc-join-tests.el                    |   2 +-
 test/lisp/erc/erc-networks-tests.el                | 557 ++++++++++++---------
 .../erc/erc-scenarios/erc-scenarios-auth-source.el |  22 +-
 .../erc-scenarios-base-association-nick.el         |   2 +-
 .../erc-scenarios-base-association-samenet.el      |   2 +-
 .../erc-scenarios-base-association.el              |   2 +-
 .../erc-scenarios-base-compat-rename-bouncer.el    |   2 +-
 .../erc-scenarios-base-misc-regressions.el         |   9 +-
 .../erc-scenarios-base-netid-bouncer-id.el         |   2 +-
 .../erc-scenarios-base-netid-bouncer-recon-base.el |   2 +-
 .../erc-scenarios-base-netid-bouncer-recon-both.el |   2 +-
 .../erc-scenarios-base-netid-bouncer-recon-id.el   |   2 +-
 .../erc-scenarios-base-netid-bouncer.el            |   2 +-
 .../erc-scenarios-base-netid-samenet.el            |   2 +-
 .../erc-scenarios/erc-scenarios-base-reconnect.el  |   2 +-
 .../erc/erc-scenarios/erc-scenarios-base-renick.el |   2 +-
 .../erc-scenarios-base-reuse-buffers.el            | 151 ++++--
 .../erc-scenarios/erc-scenarios-base-unstable.el   |   2 +-
 ...l => erc-scenarios-base-upstream-recon-soju.el} |  28 +-
 ...el => erc-scenarios-base-upstream-recon-znc.el} |  28 +-
 ...cer-recon-base.el => erc-scenarios-internal.el} |  15 +-
 .../erc-scenarios-join-auth-source.el              |  67 +++
 .../erc-scenarios-join-netid-newcmd-id.el          |   2 +-
 .../erc-scenarios-join-netid-newcmd.el             |   2 +-
 .../erc-scenarios-join-netid-recon-id.el           |   2 +-
 .../erc-scenarios-join-netid-recon.el              |   2 +-
 test/lisp/erc/erc-scenarios/erc-scenarios-misc.el  |   2 +-
 .../erc-scenarios/erc-scenarios-services-misc.el   |   2 +-
 .../resources/base/reconnect/aborted.eld           |   2 +-
 .../base/upstream-reconnect/soju-barnet.eld        |  64 +++
 .../base/upstream-reconnect/soju-foonet.eld        |  72 +++
 .../base/upstream-reconnect/znc-barnet.eld         |  93 ++++
 .../base/upstream-reconnect/znc-foonet.eld         |  86 ++++
 .../erc/erc-scenarios/resources/erc-d/erc-d-i.el   |   2 +-
 .../erc/erc-scenarios/resources/erc-d/erc-d-t.el   |   2 +-
 .../erc-d/erc-d-tests.el}                          | 147 +++---
 .../erc/erc-scenarios/resources/erc-d/erc-d-u.el   |   2 +-
 .../erc/erc-scenarios/resources/erc-d/erc-d.el     |   8 +-
 .../resources/erc-d/resources/proxy-subprocess.el  |   2 +-
 .../resources/erc-scenarios-common.el              |  64 ++-
 .../resources/join/auth-source/foonet.eld          |  33 ++
 test/lisp/erc/erc-services-tests.el                | 132 ++---
 test/lisp/erc/erc-tests.el                         |   2 +
 54 files changed, 1874 insertions(+), 945 deletions(-)

diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi
index 22e27c411e..6daa54d956 100644
--- a/doc/misc/erc.texi
+++ b/doc/misc/erc.texi
@@ -551,18 +551,16 @@ Non-interactively, it takes the following keyword 
arguments.
 @item @var{id}
 @end itemize
 
-That is, if called with the following arguments
+For example, calling the command like so
 
-@example
+@example lisp
 (erc :server "irc.libera.chat" :full-name "J. Random Hacker")
 @end example
 
-@var{server} and @var{full-name} will be set to those values, whereas
-helpers like @code{erc-compute-port} and @code{erc-compute-nick} will
-be invoked for the values of the other parameters.
-
-The optional @var{id} parameter is generally unneeded and isn't
-available interactively.  See dedicated subsection below for details.
+sets @var{server} and @var{full-name} directly while leaving the rest
+up to functions like @code{erc-compute-port}.  Note that some
+arguments can't be specified interactively.  @var{id}, in particular,
+is rarely needed (@pxref{Network Identifier}).
 
 @end defun
 
@@ -585,16 +583,16 @@ Non-interactively, it takes the following keyword 
arguments.
 @item @var{client-certificate}
 @end itemize
 
-That is, if called with the following arguments
+That is, if called in the following manner
 
-@example
+@example lisp
 (erc-tls :server "irc.libera.chat" :full-name "J. Random Hacker")
 @end example
 
-@var{server} and @var{full-name} will be set to those values, whereas
-helpers like @code{erc-compute-port} and @code{erc-compute-nick} will
-be invoked for the values of the other parameters, and
-@code{client-certificate} will be @code{nil}.
+the command will set @var{server} and @var{full-name} accordingly,
+while helpers, like @code{erc-compute-nick}, will determine other
+parameters, and some, like @code{client-certificate}, will just be
+@code{nil}.
 
 To use a certificate with @code{erc-tls}, specify the optional
 @var{client-certificate} keyword argument, whose value should be as
@@ -732,29 +730,30 @@ You can manually set another nickname with the /NICK 
command.
 
 @subheading User
 @defun erc-compute-user &optional user
-Return the first argument sent for the opening ``USER'' IRC command
-by consulting the following sources:
+Determine a suitable value to send for the first argument to the
+opening @samp{USER} IRC command by consulting the following sources:
 
 @itemize
-@item @var{user} (the argument passed to this function)
-@item The @code{erc-email-userid} option, assuming @code{erc-anonymous-login} 
is non-nil
+@item @var{user}, the argument passed to this function
+@item The option @code{erc-email-userid}, assuming @code{erc-anonymous-login}
+is non-@code{nil}
 @item The result of calling the function @code{user-login-name}
 @end itemize
 
 @end defun
 
 @defopt erc-email-userid
-A permanent username value to use for all connections.  It should be a
-string abiding by the rules of the network.
+A permanent username value to send for all connections.  It should be
+a string abiding by the rules of the network.
 @end defopt
 
 @subheading Password
 @cindex password
 
 @defopt erc-prompt-for-password
-If non-@var{nil} (the default), @kbd{M-x erc @key{RET}} prompts for a
-server password.  This only affects interactive invocations of
-@command{erc} and @command{erc-tls}.
+If non-@code{nil} (the default), @kbd{M-x erc} prompts for a server
+password.  This only affects interactive invocations of @code{erc} and
+@code{erc-tls}.
 @end defopt
 
 @noindent
@@ -768,105 +767,94 @@ machine irc.example.net login mynick password sEcReT
 @end example
 
 @noindent
-Here, @code{irc.example.net} corresponds to the @var{server} param
-passed to @command{erc} or @command{erc-tls} or provideed at the ``IRC
-server:'' prompt.  Unfortunately, specifying a network, like
-``Libera.Chat,'' or a specific network server, like
-``zirconium.libera.chat,'' won't work for this introductory exchange
-because IRC servers don't provide such information up front.
+For server passwords, that is, passwords sent for the IRC @samp{PASS}
+command, the @samp{host} field, here @code{machine irc.example.net},
+corresponds to the @var{server} parameter used by @code{erc} and
+@code{erc-tls}.  Unfortunately, specifying a network, like
+@samp{Libera.Chat}, or a specific network server, like
+@samp{platinum.libera.chat}, won't work OOTB for looking up a server
+password because such information isn't available during opening
+introductions.  Actually, ERC @emph{can} find entries with arbitrary
+@samp{host} values for any context, including server passwords, but
+that requires messing with the more advanced options below.
 
 If ERC can't find a suitable server password, it'll just skip the IRC
-``PASS'' command altogether, something users may want when using
+@samp{PASS} command altogether, something users may want when using
 CertFP or engaging NickServ via ERC's ``services'' module.  If that
-sounds like you, set the option
-@code{erc-auth-source-parameters-server-function} to @code{nil}.  Note
-that some networks and IRCds may support account-services
-authentication via server password when specified using the
-non-standard ``mynick:sEcReT'' convention.
-
-@code{auth-source} can also be used to authenticate to account
-services the traditional way, through a bot called ``NickServ''.  To
-tell ERC to do that, set the option
+sounds like you, you can also set the option
+@code{erc-auth-source-server-function} to @code{nil} to skip
+server-passwork lookup for all servers.  Note that some networks and
+IRCds may accept account-services authentication via server password
+using the nonstandard ``mynick:sEcReT'' convention.
+
+As just mentioned, you can also use @code{auth-source} to authenticate
+to account services the traditional way, through a bot called
+``NickServ''.  To tell ERC to do that, set
 @code{erc-use-auth-source-for-nickserv-password} to @code{t}.  For
-these queries, entries featuring user-provided IDs and networks are
-matched first, followed by network-specific servers and dialed
-endpoints (typically, the @var{SERVER} passed to @command{erc}). The
-following netrc-style entries appear in order of precedence:
+these and most other queries, entries featuring custom identifiers and
+networks are matched first, followed by network-specific servers and
+dialed endpoints (typically, the @var{SERVER} passed to
+@code{erc}). The following netrc-style entries appear in order of
+precedence:
 
 @example
-machine Libera/cellphone login "mynick" password sEcReT
-machine Libera.Chat login "mynick" password sEcReT
-machine zirconium.libera.chat login "mynick" password sEcReT
-machine irc.libera.chat login "mynick" password sEcReT
+machine Libera/cellphone login MyNick password sEcReT
+machine Libera.Chat login MyNick password sEcReT
+machine zirconium.libera.chat login MyNick password sEcReT
+machine irc.libera.chat login MyNick password sEcReT
 @end example
 
-If this fallback stuff doesn't appeal to you, see the option
-@code{erc-auth-source-parameters-server-function} and friends just
-below.  These let you decide based on context how a connection's
-parameters should map to auth-source queries.  But most users can just
-ignore all the details and get by with something like the following as
-values:
-
-@example
-(defun my-erc-auth-source-server (&rest _)
-  (list :host "MyHost"))
-
-;; or
+@noindent
+Remember that field labels vary per backend, so @samp{machine} (in
+netrc's case) maps to auth-source's generalized notion of a host,
+hence the @samp{:host} keyword property.  Also, be sure and mind the
+syntax of your chosen backend medium.  For example, always quote
+channel names in a netrc file.
+
+If this all seems overly nuanced or just plain doesn't appeal to you,
+see options @code{erc-auth-source-services-function} and friends just
+below.  These let you query auth-source your way.  Most users can
+simply ignore the passed-in arguments and get by with something like
+the following:
 
-(defun my-erc-auth-source-services (&rest _)
-  (list :host (read-string "Auth-source host: ")))
-@end example
+@lisp
+(defun my-fancy-auth-source-func (&rest _)
+  (let* ((host (read-string "host: " nil nil "default"))
+         (pass (auth-source-pick-first-password :host host)))
+    (if (and pass (string-search "libera" host))
+        (concat "MyNick:" pass)
+      pass)))
+@end lisp
 
-ERC also consults @code{auth-source} to find ``keys'' that may be
-required by certain channels you join.  When modifying a traditional
-@code{auth-source} entry for such use, put the channel name, for
-example ``##my-plus-k-chan,'' in the value of the ``user'' field (also
-known as the ``login'' field). The actual key goes in the ``password''
-(or ``secrets'') field.
+Lastly, ERC also consults @code{auth-source} to find ``keys'' that may
+be required by certain channels you join.  When modifying a
+traditional @code{auth-source} entry for this purpose, put the channel
+name in the @samp{user} field (for example, @samp{login "#fsf"}, in
+netrc's case). The actual key goes in the @samp{password} (or
+@samp{secret}) field.
 
+@noindent
 For details, @pxref{Top,,auth-source, auth, Emacs auth-source Library}.
 
-@defopt erc-auth-source-parameters-server-function
+@defopt erc-auth-source-server-function
 @end defopt
-@defopt erc-auth-source-parameters-services-function
+@defopt erc-auth-source-services-function
 @end defopt
-@defopt erc-auth-source-parameters-join-function
+@defopt erc-auth-source-join-function
 
 ERC calls these functions with keyword arguments recognized by
-`auth-source-search', namely, those deemed most relevant to the
-current context.  For example, with NickServ queries, @code{:user}
-will be the ``desired'' nickname rather than the current one.
+@code{auth-source-search}, namely, those deemed most relevant to the
+current context, if any.  For example, with NickServ queries,
+@code{:user} is the ``desired'' nickname rather than the current one.
 Generalized names, like @code{:user} and @code{:host}, are always used
 over back-end specific ones, like @code{:login} or @code{:machine}.
-ERC expects a refined plist in return.
-
-The ordering of the returned pairs influences how results are filtered
-as does the ordering of the members of any individual composite
-values.  If necessary, the former (pairs) takes priority over the
-latter (values).  For example, if the function returns
-
-@example
-(:host (foo bar) :port (123 456))
-@end example
-
-the secret from an auth-source entry of host foo and port 456 will be
-chosen over another of host bar and port 123.  However, if the
-function returns
-
-@example
-(:port (123 456) :host (foo bar))
-@end example
-
-the opposite will be true.  In both cases, two entries with the same
-host but different ports would see the one with port 123 being
-selected.  Much the same would happen for entries sharing only a port:
-the one with host foo would win.
-
-Some auth-source back ends may not be compatible; netrc, plstore,
-json, and secrets are currently supported.
+ERC expects a string to use as the secret or nil, if the search fails.
 
-The default value for all variants is currently
-@code{erc-auth-source-determine-params-merge}.
+The default value for all three options is the function
+@code{erc-auth-source-search}.  It tries to merge relevant contextual
+params with those provided or discovered from the logical connection
+or the underlying transport.  Some auth-source back ends may not be
+compatible; netrc, plstore, json, and secrets are currently supported.
 @end defopt
 
 @subheading Full name
@@ -894,21 +882,23 @@ This can be either a string or a function to call.
 
 
 @subheading ID
-ERC uses an abstract designation called a ``network identifier'' for
-referring to a connection internally.  While normally derived from a
-combination of logical and physical connection parameters, an ID can
-also be explicitly provided via an entry-point command (like
-@command{erc-tls}). Use this in rare situations where ERC would
-otherwise have trouble discerning between connections.
-
-One such situation might see multiple connections to the same network
-using the same nick but different (non-standard) "device" identifiers,
-which some bouncers may support.  Another might be to mimic the
-experience offered by popular standalone clients, which normally offer
-``named'' persistent configurations with server buffers reflecting
-those names.  Yet another use case might involve third-party code
-needing to identify a connection unequivocally but in a human-friendly
-way suitable for UI components.
+@anchor{Network Identifier}
+
+ERC uses an abstract designation called a @dfn{network context
+identifier} for referring to a connection internally.  While normally
+derived from a combination of logical and physical connection
+parameters, an ID can also be explicitly provided via an entry-point
+command (like @code{erc-tls}). Use this in rare situations where ERC
+would otherwise have trouble discerning between connections.
+
+One such situation might arise when using multiple connections to the
+same network with the same nick but different (nonstandard) "device"
+identifiers, which some bouncers may support.  Another might be when
+mimicking the experience offered by popular standalone clients, which
+normally offer ``named'' persistent configurations with server buffers
+reflecting those names.  Yet another use case might involve
+third-party code needing to identify a connection unequivocally but in
+a human-friendly way suitable for UI components.
 
 When providing an ID as an entry-point argument, strings and symbols
 make the most sense, but any reasonably printable object is
diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index bdcd943c37..0ac3df1ba1 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -12,6 +12,92 @@ extensible IRC (Internet Relay Chat) client distributed with
 GNU Emacs since Emacs version 22.1.
 
 
+* Changes in ERC 5.5
+
+** Smarter buffer naming for withstanding collisions.
+ERC buffers now remain tied to their logical network contexts, even
+while offline.  These associations can survive regional server changes
+and the intercession of proxies.  As has long been practiced in other
+areas of Emacs, "uniquified" buffer renaming prevents collisions
+between buffers of different contexts.  Potential avenues for
+confusion remain but will die out with the adoption of emerging
+protocol extensions.
+
+** Option 'erc-rename-buffers' deprecated.
+The promise of its old "on" state are now fully realized and enabled
+permanently by default.  Its old behavior when disabled has been
+preserved and will remain available (with warnings) for years to come.
+
+** Option 'erc-reuse-buffers' deprecated.
+This ancient option has been a constant source of confusion, as
+exhibited most recently when its "disabled" meaning was partially
+inverted.  Introduced in ERC 5.4 (Emacs 28.1), this regression saw
+existing channel buffers transparently reassociated instead of created
+anew.  The pre-5.4 "disabled" behavior has been restored and will
+remain accessible for the foreseeable future, warts and all (e.g.,
+with its often superfluous "/DIALED-HOST" suffixing always present).
+
+** Convention adopted for auditioning edge functionality.
+Objects bearing names like "erc--X-foo" or "erc-somemod--X-foo" will
+be offered as trial balloons for potential export and as stopgaps for
+overdue but immature fixes and features.  (If you dare try them,
+please give feedback!)  This move comes out of desperation as we focus
+on much needed internal remediation while trying to meet baseline
+expectations in the face of mounting pressure from networks and users.
+
+** Tighter auth-source integration with bigger changes on the horizon.
+The days of hit-and-miss auth-source queries are hopefully behind us.
+With the overhaul of the services module temporarily shelved and the
+transition to SASL-based authentication still underway, users may feel
+left in the lurch to endure yet another release cycle of backtick
+hell.  For some, auth-source may provide a workaround in the form of
+nonstandard server passwords.  See the "Connection" node in the manual
+under the subheading "Password".  If you require SASL immediately,
+please participate in ERC development by volunteering to try (and give
+feedback on) edge features, one of which is SASL.  All known external
+offerings, past and present, are makeshift efforts whose use is
+discouraged.  This includes the instructions on Libera.Chat's website.
+
+** Username argument for entry point commands.
+Commands 'erc' and 'erc-tls' now accept a ':user' keyword argument,
+which, when present, becomes the first argument passed to the "USER"
+IRC command.  The traditional way of setting this globally, via
+'erc-email-userid', is still honored.
+
+** Additional display options for updated buffers.
+Additional flexibility is now available for controlling the behavior
+of newly created target buffers, especially during reconnection.
+
+** Miscellaneous behavioral changes impacting the user experience.
+A bug has been fixed that saw prompts being mangled, doubled, or
+erased in server buffers upon disconnection.  Instead, prompts now
+collapse into an alternate form designated by the option
+'erc-prompt-hidden'.  Behavior differs for query and channel buffers
+but can be fine-tuned via the repurposed, formerly abandoned option
+'erc-hide-prompt'.
+
+A bug has been fixed affecting users of the Soju bouncer: outgoing
+messages during periods of heavy traffic no longer disappear.
+
+Although rare, server passwords containing white space are now handled
+correctly.
+
+** Miscellaneous behavioral changes in the library API.
+The function 'erc-network' always returns non-nil in server and target
+buffers belonging to a successfully established IRC connection, even
+after that connection is closed.  In 5.4, support for network symbols
+as keys was added for 'erc-autojoin-channels-alist'.  This has been
+extended to include explicit symbols passed to 'erc-tls' and 'erc' as
+so-called network-context identifiers via a new ':id' keyword.  The
+latter carries wider significance beyond autojoin and can be used for
+uniquely and unequivocally identifying a connection.  The function
+'erc-auto-query', unused internally, and basically inscrutable when
+read, has been deprecated with no public replacement.  Which raises
+the issue: if you use ERC as a library and need something that's only
+offered internally, please lobby for its exporting by writing to
+emacs-erc@gnu.org.
+
+
 * Changes in ERC 5.4.1
 
 ** No user-visible changes since ERC 5.4, but a few tweaks in some ERC
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 34de5d48b9..7e174a6fd1 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -186,7 +186,7 @@ TOPICLEN=160 - maximum allowed topic length
 WALLCHOPS - supports sending messages to all operators in a channel")
 
 (defvar-local erc--isupport-params nil
-  "Hash map of isupport params.
+  "Hash map of \"ISUPPORT\" params.
 Keys are symbols.  Values are lists of zero or more strings with hex
 escapes removed.")
 
@@ -196,11 +196,9 @@ escapes removed.")
   "Mapping of server buffers to their specific ping timer.")
 
 (defvar-local erc-server-connected nil
-  "Non-nil if the current buffer has been used by ERC to establish
-an IRC connection.
-
-If you wish to determine whether an IRC connection is currently
-active, use the `erc-server-process-alive' function instead.")
+  "Non-nil if the current buffer belongs to an active IRC connection.
+To determine whether an underlying transport is connected, use the
+function `erc-server-process-alive' instead.")
 
 (defvar-local erc-server-reconnect-count 0
   "Number of times we have failed to reconnect to the current server.")
@@ -604,7 +602,10 @@ Make sure you are in an ERC buffer when running this."
                   erc-session-user-full-name t erc-session-password
                   nil nil nil erc-session-client-certificate
                   erc-session-username
-                  (erc-networks--id-given erc-networks--id))))))
+                  (erc-networks--id-given erc-networks--id))
+        (unless (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+                  erc-reuse-buffers)
+          (cl-assert (not (eq buffer (current-buffer)))))))))
 
 (defun erc-server-delayed-reconnect (buffer)
   (if (buffer-live-p buffer)
@@ -1241,7 +1242,7 @@ Would expand to:
                         aliases))
   (let* ((hook-name (intern (format "erc-server-%s-functions" name)))
          (fn-name (intern (format "erc-server-%s" name)))
-         (hook-doc (format-message "\
+         (hook-doc (format "\
 %sHook called upon receiving a %%s server response.
 Each function is called with two arguments, the process associated
 with the response and the parsed response.  If the function returns
@@ -1252,7 +1253,7 @@ See also `%s'."
                                (concat extra-var-doc "\n\n")
                              "")
                            fn-name))
-         (fn-doc (format-message "\
+         (fn-doc (format "\
 %sHandler for a %s server response.
 PROC is the server process which returned the response.
 PARSED is the actual response as an `erc-response' struct.
@@ -1672,7 +1673,7 @@ Then display the welcome message."
          (split-string value ",")
        (list value)))))
 
-;; FIXME move to erc-compat (once it's been fully reinstated)
+;; FIXME move to erc-compat (once we decide how to load it)
 (defalias 'erc--with-memoization
   (cond
    ((fboundp 'with-memoization) #'with-memoization) ; 29.1
@@ -1683,8 +1684,8 @@ Then display the welcome message."
   "Return an item for \"ISUPPORT\" token KEY, a symbol.
 When a lookup fails return nil.  Otherwise return a list whose CAR is
 KEY and whose CDR is zero or more strings.  With SINGLE, just return the
-first value, if any.  This is potentially ambiguous and only useful for
-tokens supporting a single primitive value."
+first value, if any.  The latter is potentially ambiguous and only
+useful for tokens supporting a single primitive value."
   (if-let* ((table (or erc--isupport-params
                        (erc-with-server-buffer erc--isupport-params)))
             (value (erc--with-memoization (gethash key table)
diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el
index 0e7d0d584f..aeada705c4 100644
--- a/lisp/erc/erc-button.el
+++ b/lisp/erc/erc-button.el
@@ -125,7 +125,7 @@ longer than `erc-fill-column'."
   ;; a button, it makes no sense to optimize performance by
   ;; bytecompiling lambdas in this alist.  On the other hand, it makes
   ;; things hard to maintain.
-  '(('nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0)
+  '((nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0)
     (erc-button-url-regexp 0 t browse-url-button-open-url 0)
     ("<URL: *\\([^<> ]+\\) *>" 0 t browse-url-button-open-url 1)
 ;;; ("(\\(\\([^~\n \t@][^\n \t@]*\\)@\\([a-zA-Z0-9.:-]+\\)\\)" 1 t finger 2 3)
@@ -158,12 +158,12 @@ REGEXP is the string matching text around the button or a 
symbol
   strings, or an alist with the strings in the car.  Note that
   entries in lists or alists are considered to be nicks or other
   complete words.  Therefore they are enclosed in \\< and \\>
-  while searching.  REGEXP can also be the quoted symbol
-  \\='nicknames, which matches the nickname of any user on the
+  while searching.  REGEXP can also be the symbol
+  `nicknames', which matches the nickname of any user on the
   current server.
 
 BUTTON is the number of the regexp grouping actually matching the
-  button.  This is ignored if REGEXP is \\='nicknames.
+  button.  This is ignored if REGEXP is `nicknames'.
 
 FORM is a Lisp expression which must eval to true for the button to
   be added.
@@ -174,17 +174,15 @@ CALLBACK is the function to call when the user push this 
button.
 
 PAR is a number of a regexp grouping whose text will be passed to
   CALLBACK.  There can be several PAR arguments.  If REGEXP is
-  \\='nicknames, these are ignored, and CALLBACK will be called with
+  `nicknames', these are ignored, and CALLBACK will be called with
   the nickname matched as the argument."
-  :version "24.1"                       ; remove finger (bug#4443)
+  :version "29.1"
   :type '(repeat
           (list :tag "Button"
                 (choice :tag "Matches"
                         regexp
                         (variable :tag "Variable containing regexp")
-                        ;; FIXME It really does mean 'nicknames
-                        ;; rather than just nicknames.
-                        (const :tag "Nicknames" 'nicknames))
+                        (const :tag "Nicknames" nicknames))
                 (integer :tag "Number of the regexp section that matches")
                 (choice :tag "When to buttonize"
                         (const :tag "Always" t)
@@ -256,7 +254,9 @@ specified by `erc-button-alist'."
             regexp)
         (erc-button-remove-old-buttons)
         (dolist (entry alist)
-          (if (equal (car entry) (quote (quote nicknames)))
+          (if (or (eq (car entry) 'nicknames)
+                  ;; Old form retained for backward compatibility.
+                  (equal (car entry) (quote 'nicknames)))
               (erc-button-add-nickname-buttons entry)
             (progn
               (setq regexp (or (and (stringp (car entry)) (car entry))
diff --git a/lisp/erc/erc-dcc.el b/lisp/erc/erc-dcc.el
index 59bfd24603..ff486b2d4e 100644
--- a/lisp/erc/erc-dcc.el
+++ b/lisp/erc/erc-dcc.el
@@ -43,7 +43,7 @@
 ;;  /dcc chat nick - Either accept pending chat offer from nick, or offer
 ;;                   DCC chat to nick
 ;;  /dcc close type [nick] - Close DCC connection (SEND/GET/CHAT) with nick
-;;  /dcc get nick [file] - Accept DCC offer from nick
+;;  /dcc get [-t][-s] nick [file] - Accept DCC offer from nick
 ;;  /dcc list - List all DCC offers/connections
 ;;  /dcc send nick file - Offer DCC SEND to nick
 
@@ -105,7 +105,11 @@ Looks like:
  :file - for outgoing sends, the full path to the file.  For incoming sends,
          the suggested filename or vetted filename
 
- :size - size of the file, may be nil on incoming DCCs")
+ :size - size of the file, may be nil on incoming DCCs
+
+ :secure - optional item indicating sender support for TLS
+
+ :turbo - optional item indicating sender support for TSEND")
 
 (defun erc-dcc-list-add (type nick peer parent &rest args)
   "Add a new entry of type TYPE to `erc-dcc-list' and return it."
@@ -119,12 +123,13 @@ Looks like:
 ;; more: the entry data from erc-dcc-list for this particular process.
 (defvar erc-dcc-connect-function 'erc-dcc-open-network-stream)
 
-(defun erc-dcc-open-network-stream (procname buffer addr port _entry)
+(defun erc-dcc-open-network-stream (procname buffer addr port entry)
   ;; FIXME: Time to try activating this again!?
   (if nil;  (fboundp 'open-network-stream-nowait)  ;; this currently crashes
                                                    ;; cvs emacs
       (open-network-stream-nowait procname buffer addr port)
-    (open-network-stream procname buffer addr port)))
+    (open-network-stream procname buffer addr port
+                         :type (and (plist-get entry :secure) 'tls))))
 
 (erc-define-catalog
  'english
@@ -144,13 +149,14 @@ Looks like:
    (dcc-get-bytes-received . "DCC: %f: %b bytes received")
    (dcc-get-complete
     . "DCC: file %f transfer complete (%s bytes in %t seconds)")
+   (dcc-get-failed . "DCC: file %f transfer failed at %s of %v in %t seconds")
    (dcc-get-cmd-aborted . "DCC: Aborted getting %f from %n")
    (dcc-get-file-too-long
     . "DCC: %f: File longer than sender claimed; aborting transfer")
    (dcc-get-notfound . "DCC: %n hasn't offered %f for DCC transfer")
-   (dcc-list-head . "DCC: From      Type  Active  Size            Filename")
-   (dcc-list-line . "DCC: --------  ----  ------  --------------  --------")
-   (dcc-list-item . "DCC: %-8n  %-4t  %-6a  %-14s  %f")
+   (dcc-list-head . "DCC: From      Type  Active  Size               Filename")
+   (dcc-list-line . "DCC: --------  ----  ------  -----------------  --------")
+   (dcc-list-item . "DCC: %-8n  %-4t  %-6a  %-17s  %f%u")
    (dcc-list-end  . "DCC: End of list.")
    (dcc-malformed . "DCC: error: %n (%u@%h) sent malformed request: %q")
    (dcc-privileged-port
@@ -195,7 +201,7 @@ compared with `erc-nick-equal-p' which is IRC 
case-insensitive."
                       (erc-extract-nick test)
                       (erc-extract-nick val)))
                 ;; not a nick
-                (eq test val)
+                (equal test val)
                 (setq cont nil))))
         (if cont
             (setq result elt)
@@ -505,8 +511,12 @@ At least one of TYPE and NICK must be provided."
 FILE is the filename.  If FILE is split into multiple arguments,
 re-join the arguments, separated by a space.
 PROC is the server process."
-  (setq file (and file (mapconcat #'identity file " ")))
-  (let* ((elt (erc-dcc-member :nick nick :type 'GET))
+  (let* ((args (seq-group-by (lambda (s) (eq ?- (aref s 0))) (cons nick file)))
+         (flags (prog1 (cdr (assq t args))
+                  (setq args (cdr (assq nil args))
+                        nick (pop args)
+                        file (and args (mapconcat #'identity args " ")))))
+         (elt (erc-dcc-member :nick nick :type 'GET :file file))
          (filename (or file (plist-get elt :file) "unknown")))
     (if elt
         (let* ((file (read-file-name
@@ -526,7 +536,13 @@ PROC is the server process."
                     'dcc-get-cmd-aborted
                     ?n nick ?f filename)))
                 (t
-                 (erc-dcc-get-file elt file proc))))
+                 (erc-dcc-get-file elt file proc)))
+          (when (member "-s" flags)
+            (setq erc-dcc-list (cons (plist-put elt :secure t)
+                                     (delq elt erc-dcc-list))))
+          (when (member "-t" flags)
+            (setq erc-dcc-list (cons (plist-put elt :turbo t)
+                                     (delq elt erc-dcc-list)))))
       (erc-display-message
        nil '(notice error) 'active
        'dcc-get-notfound ?n nick ?f filename))))
@@ -564,6 +580,7 @@ It lists the current state of `erc-dcc-list' in an easy to 
read manner."
               (process-status (plist-get elt :peer))
             "no")
        ?s (concat size
+                  ;; FIXME consider uniquified names, e.g., foo.bin<2>
                   (if (and (eq 'GET (plist-get elt :type))
                            (plist-member elt :file)
                            (buffer-live-p (get-buffer (plist-get elt :file)))
@@ -575,7 +592,12 @@ It lists the current state of `erc-dcc-list' in an easy to 
read manner."
                         (format " (%d%%)"
                                 (floor (* 100.0 byte-count)
                                        (plist-get elt :size))))))
-       ?f (or (and (plist-member elt :file) (plist-get elt :file)) "")))
+       ?f (or (and (plist-member elt :file) (plist-get elt :file)) "")
+       ?u (if-let* ((flags (concat (and (plist-get elt :turbo) "t")
+                                   (and (plist-get elt :secure) "s")))
+                    ((not (string-empty-p flags))))
+              (concat " (" flags ")")
+            "")))
     (erc-display-message
      nil 'notice 'active
      'dcc-list-end)
@@ -602,6 +624,10 @@ separated by a space."
 
 (defvar erc-dcc-query-handler-alist
   '(("SEND" . erc-dcc-handle-ctcp-send)
+    ("TSEND" . erc-dcc-handle-ctcp-send)
+    ("SSEND" . erc-dcc-handle-ctcp-send)
+    ("TSSEND" . erc-dcc-handle-ctcp-send)
+    ("STSEND" . erc-dcc-handle-ctcp-send)
     ("CHAT" . erc-dcc-handle-ctcp-chat)))
 
 ;;;###autoload
@@ -620,12 +646,16 @@ that subcommand."
        ?q query ?n nick ?u login ?h host))))
 
 (defconst erc-dcc-ctcp-query-send-regexp
-  (concat "^DCC SEND \\(?:"
+  (rx bot "DCC " (group-n 6 (: (** 0 2 (any "TS")) "SEND")) " "
           ;; Following part matches either filename without spaces
           ;; or filename enclosed in double quotes with any number
           ;; of escaped double quotes inside.
-          "\"\\(\\(?:\\\\\"\\|[^\"\\]\\)+\\)\"\\|\\([^ ]+\\)"
-          "\\) \\([0-9]+\\) \\([0-9]+\\) *\\([0-9]*\\)"))
+      (: (or (: ?\" (group-n 1 (+ (or (: ?\\ ?\") (not (any ?\" ?\\))))) ?\")
+             (group-n 2 (+ (not " ")))))
+      (: " " (group-n 3 (+ digit))
+         " " (group-n 4 (+ digit))
+         (* " ") (group-n 5 (* digit)))
+      eot))
 
 (define-inline erc-dcc-unquote-filename (filename)
   (inline-quote
@@ -650,12 +680,14 @@ It extracts the information about the dcc request and 
adds it to
        'dcc-request-bogus
        ?r "SEND" ?n nick ?u login ?h host))
      ((string-match erc-dcc-ctcp-query-send-regexp query)
-      (let ((filename
-             (or (match-string 2 query)
-                 (erc-dcc-unquote-filename (match-string 1 query))))
-            (ip       (erc-decimal-to-ip (match-string 3 query)))
-            (port     (match-string 4 query))
-            (size     (match-string 5 query)))
+      (let* ((filename (or (match-string 2 query)
+                           (erc-dcc-unquote-filename (match-string 1 query))))
+             (ip (erc-decimal-to-ip (match-string 3 query)))
+             (port (match-string 4 query))
+             (size (match-string 5 query))
+             (sub (substring (match-string 6 query) 0 -4))
+             (secure (seq-contains-p sub ?S #'eq))
+             (turbo (seq-contains-p sub ?T #'eq)))
         ;; FIXME: a warning really should also be sent
         ;; if the ip address != the host the dcc sender is on.
         (erc-display-message
@@ -672,7 +704,9 @@ It extracts the information about the dcc request and adds 
it to
          'GET (format "%s!%s@%s" nick login host)
          nil proc
          :ip ip :port port :file filename
-         :size (string-to-number size))
+         :size (string-to-number size)
+         :turbo (and turbo t)
+         :secure (and secure t))
         (if (and (eq erc-dcc-send-request 'auto)
                  (erc-dcc-auto-mask-p (format "\"%s!%s@%s\"" nick login host)))
             (erc-dcc-get-file (car erc-dcc-list) filename proc))))
@@ -920,8 +954,7 @@ and making the connection."
             (inhibit-file-name-operation 'write-region))
         (write-region (point) (point) erc-dcc-file-name nil 'nomessage))
 
-      (setq erc-server-process parent-proc
-            erc-dcc-entry-data entry)
+      (setq erc-server-process parent-proc)
       (setq erc-dcc-byte-count 0)
       (setq proc
             (funcall erc-dcc-connect-function
@@ -935,8 +968,8 @@ and making the connection."
 
       (set-process-filter proc #'erc-dcc-get-filter)
       (set-process-sentinel proc #'erc-dcc-get-sentinel)
-      (setq entry (plist-put entry :start-time (erc-current-time)))
-      (setq entry (plist-put entry :peer proc)))))
+      (setq erc-dcc-entry-data (plist-put (plist-put entry :peer proc)
+                                          :start-time (erc-current-time))))))
 
 (defun erc-dcc-append-contents (buffer _file)
   "Append the contents of BUFFER to FILE.
@@ -952,6 +985,16 @@ The contents of the BUFFER will then be erased."
       (setq erc-dcc-byte-count (+ (buffer-size) erc-dcc-byte-count))
       (erase-buffer))))
 
+;; If people really need this, we can convert it into a proper option.
+
+(defvar erc-dcc--X-send-final-turbo-ack nil
+  "Workaround for maverick turbo senders that only require a final ACK.
+The only known culprit is WeeChat, with its xfer.network.fast_send
+option, which is on by default.  Leaving this set to nil and calling
+/DCC GET -t works just fine, but WeeChat sees it as a failure even
+though the file arrives in its entirety.  Setting this to t may
+alleviate such problems.")
+
 (defun erc-dcc-get-filter (proc str)
   "This is the process filter for transfers from other clients to this one.
 It reads incoming bytes from the network and stores them in the DCC
@@ -986,31 +1029,43 @@ rather than every 1024 byte block, but nobody seems to 
care."
          'dcc-get-file-too-long
          ?f (file-name-nondirectory (buffer-name)))
         (delete-process proc))
-       (t
-        (process-send-string
-         proc (erc-pack-int received-bytes)))))))
-
+       ;; Some senders want us to hang up.  Only observed w. TSEND.
+       ((and (plist-get erc-dcc-entry-data :turbo)
+             (= received-bytes (plist-get erc-dcc-entry-data :size)))
+        (when erc-dcc--X-send-final-turbo-ack
+          (process-send-string proc (erc-pack-int received-bytes)))
+        (delete-process proc))
+       ((not (or (plist-get erc-dcc-entry-data :turbo)
+                 (process-get proc :reportingp)))
+        (process-put proc :reportingp t)
+        (process-send-string proc (erc-pack-int received-bytes))
+        (process-put proc :reportingp nil))))))
 
-(defun erc-dcc-get-sentinel (proc _event)
+(defun erc-dcc-get-sentinel (proc event)
   "This is the process sentinel for CTCP DCC SEND connections.
 It shuts down the connection and notifies the user that the
 transfer is complete."
   ;; FIXME, we should look at EVENT, and also check size.
+  (unless (member event '("connection broken by remote peer\n"
+                          "deleted\n"))
+    (lwarn 'erc :warning "Unexpected sentinel event %S for %s"
+           (string-trim-right event) proc))
   (with-current-buffer (process-buffer proc)
     (delete-process proc)
     (setq erc-dcc-list (delete erc-dcc-entry-data erc-dcc-list))
     (unless (= (point-min) (point-max))
       (erc-dcc-append-contents (current-buffer) erc-dcc-file-name))
-    (erc-display-message
-     nil 'notice erc-server-process
-     'dcc-get-complete
-     ?f erc-dcc-file-name
-     ?s (number-to-string erc-dcc-byte-count)
-     ?t (format "%.0f"
-                (erc-time-diff (plist-get erc-dcc-entry-data :start-time)
-                               nil))))
-  (kill-buffer (process-buffer proc))
-  (delete-process proc))
+    (let ((done (= erc-dcc-byte-count (plist-get erc-dcc-entry-data :size))))
+      (erc-display-message
+       nil (if done 'notice '(notice error)) erc-server-process
+       (if done 'dcc-get-complete 'dcc-get-failed)
+       ?v (plist-get erc-dcc-entry-data :size)
+       ?f erc-dcc-file-name
+       ?s (number-to-string erc-dcc-byte-count)
+       ?t (format "%.0f"
+                  (erc-time-diff (plist-get erc-dcc-entry-data :start-time)
+                                 nil))))
+    (kill-buffer)))
 
 ;;; CHAT handling
 
diff --git a/lisp/erc/erc-join.el b/lisp/erc/erc-join.el
index 01dceffdde..b4044548e8 100644
--- a/lisp/erc/erc-join.el
+++ b/lisp/erc/erc-join.el
@@ -55,11 +55,16 @@
 Every element in the alist has the form (SERVER . CHANNELS).
 SERVER is a regexp matching the server, and channels is the list
 of channels to join.  SERVER can also be a symbol, in which case
-it is matched against the value of `erc-network' instead of
+it's matched against a non-nil `:id' passed to `erc' or `erc-tls'
+when connecting or the value of the current `erc-network' instead of
 `erc-server-announced-name' or `erc-session-server' (this can be
 useful when connecting to an IRC proxy that relays several
 networks under the same server).
 
+Note that for historical reasons, this option is mutated at runtime,
+which is regrettable but here to stay.  Please double check the value
+before saving it to a `custom-file'.
+
 If the channel(s) require channel keys for joining, the passwords
 are found via auth-source.  For instance, if you use ~/.authinfo
 as your auth-source backend, then put something like the
@@ -121,14 +126,13 @@ This is called from a timer set up by 
`erc-autojoin-channels'."
       (erc-autojoin-channels server nick))))
 
 (defun erc-autojoin-server-match (candidate)
-  "Match the current session ID or server against CANDIDATE.
+  "Match the current network ID or server against CANDIDATE.
 CANDIDATE is a key from `erc-autojoin-channels-alist'.  Return the
-matching entity, either a string or a non-nil symbol, in the case of a
-network or a session ID.  Return nil on failure."
+matching entity, either a string or a non-nil symbol (in the case of a
+network or a network ID).  Return nil on failure."
   (if (symbolp candidate)
-      (when-let ((esid (erc-networks--id-symbol erc-networks--id))
-                 ((eq esid candidate)))
-        esid)
+      (eq (or (erc-networks--id-given erc-networks--id) (erc-network))
+          candidate)
     (when (stringp candidate)
       (string-match-p candidate (or erc-server-announced-name
                                     erc-session-server)))))
@@ -171,19 +175,29 @@ Respects `erc-autojoin-domain-only'."
        (match-string 1 server)
       server)))
 
-(defun erc-autojoin-add (proc parsed)
-  "Add the channel being joined to `erc-autojoin-channels-alist'."
+(defun erc-autojoin--mutate (proc parsed remove)
   (when-let* ((nick (car (erc-parse-user (erc-response.sender parsed))))
               ((erc-current-nick-p nick))
               (chnl (car (erc-response.command-args parsed)))
               (elem (or (and (erc--valid-local-channel-p chnl)
                              (regexp-quote erc-server-announced-name))
-                        (erc-networks--id-symbol erc-networks--id)
+                        (erc-networks--id-given erc-networks--id)
+                        (erc-network)
                         (with-current-buffer (process-buffer proc)
-                          (erc-autojoin-current-server)))))
-    (cl-pushnew chnl (alist-get elem erc-autojoin-channels-alist
-                                nil nil (if (symbolp elem) #'eq #'equal))
-                :test #'equal))
+                          (erc-autojoin-current-server))))
+              (test (if (symbolp elem) #'eq #'equal)))
+    (if remove
+        (let ((cs (delete chnl (assoc-default elem erc-autojoin-channels-alist
+                                              test))))
+          (setf (alist-get elem erc-autojoin-channels-alist nil (null cs) test)
+                cs))
+      (cl-pushnew chnl
+                  (alist-get elem erc-autojoin-channels-alist nil nil test)
+                  :test #'equal))))
+
+(defun erc-autojoin-add (proc parsed)
+  "Add the channel being joined to `erc-autojoin-channels-alist'."
+  (erc-autojoin--mutate proc parsed nil)
   ;; We must return nil to tell ERC to continue running the other
   ;; functions.
   nil)
@@ -192,19 +206,7 @@ Respects `erc-autojoin-domain-only'."
 
 (defun erc-autojoin-remove (proc parsed)
   "Remove the channel being left from `erc-autojoin-channels-alist'."
-  (when-let* ((nick (car (erc-parse-user (erc-response.sender parsed))))
-              ((erc-current-nick-p nick))
-              (chnl (car (erc-response.command-args parsed)))
-              (elem (or (and (erc--valid-local-channel-p chnl)
-                             (regexp-quote erc-server-announced-name))
-                        (erc-networks--id-symbol erc-networks--id)
-                        (with-current-buffer (process-buffer proc)
-                          (erc-autojoin-current-server))))
-              (test (if (symbolp elem) #'eq #'equal)))
-    (let ((chans (delete chnl (assoc-default elem erc-autojoin-channels-alist
-                                             test))))
-      (setf (alist-get elem erc-autojoin-channels-alist nil (null chans) test)
-            chans)))
+  (erc-autojoin--mutate proc parsed 'remove)
   ;; We must return nil to tell ERC to continue running the other
   ;; functions.
   nil)
diff --git a/lisp/erc/erc-lang.el b/lisp/erc/erc-lang.el
index b65f4dbf6a..d059caf5a3 100644
--- a/lisp/erc/erc-lang.el
+++ b/lisp/erc/erc-lang.el
@@ -32,10 +32,8 @@
 
 (require 'erc)
 
-;; FIXME: It's ISO 639-1, not ISO 638.  ISO 638 is for paper, board and pulps.
-;; The Lisp variable should be renamed.
-
-(defvar iso-638-languages
+(define-obsolete-variable-alias 'iso-638-languages 'iso-639-1-languages "29.1")
+(defvar iso-639-1-languages
   '(("aa" . "Afar")
     ("ab" . "Abkhazian")
     ("af" . "Afrikaans")
@@ -197,12 +195,12 @@ Normungsinstitut (ON), Postfach 130, A-1021 Vienna, 
Austria.")
 (defun language (code)
   "Return the language name for the ISO CODE."
   (interactive (list (completing-read "ISO language code: "
-                                     iso-638-languages)))
-  (message "%s" (cdr (assoc code iso-638-languages))))
+                                     iso-639-1-languages)))
+  (message "%s" (cdr (assoc code iso-639-1-languages))))
 
 (defun erc-cmd-LANG (language)
   "Display the language name for the language code given by LANGUAGE."
-  (let ((lang (cdr (assoc language iso-638-languages))))
+  (let ((lang (cdr (assoc language iso-639-1-languages))))
     (erc-display-message
      nil 'notice 'active
      (or lang (concat language ": No such domain"))))
diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el
index 0c9c2b41b2..d4af2c0124 100644
--- a/lisp/erc/erc-networks.el
+++ b/lisp/erc/erc-networks.el
@@ -732,23 +732,20 @@ MATCHER is used to find a corresponding network to a 
server while
   "The name of the network you are connected to (a symbol).")
 
 
-;;;; Identifying connections
+;;;; Identifying session context
 
 ;; This section is concerned with identifying and managing the
 ;; relationship between an IRC connection and its unique identity on a
 ;; given network (as seen by that network's nick-granting system).
 ;; This relationship is quasi-permanent and transcends IRC connections
-;; and Emacs sessions.  As of early 2022, whether a user is
-;; authenticated (logged in to an account) remains orthogonal to their
-;; network identity from a client's perspective.  ERC must be equipped
-;; to adapt should this ever change.
-;;
-;; While a connection is normally associated with exactly one nick,
-;; some networks (or intermediaries) may allow multiple client
-;; instances for the same nick by combining instance activity into a
-;; single, unique presence for presenting to other users.  And since
-;; state syncing may happen independently or be coordinated in some
-;; fashion, ERC must be prepared to handle any combination thereof.
+;; and Emacs sessions.  As of mid 2022, only nicknames matter, and
+;; whether a user is authenticated does not directly impact network
+;; identity from a client's perspective.  However, ERC must be
+;; equipped to adapt should this ever change.  And while a connection
+;; is normally associated with exactly one nick, some networks (or
+;; intermediaries) may allow multiple clients to control the same nick
+;; by combining instance activity into a single logical client.  ERC
+;; must be limber enough to handle such situations.
 
 (defvar-local erc-networks--id nil
   "Server-local instance of its namesake struct.
@@ -758,11 +755,12 @@ Also shared among all target buffers for a given 
connection.  See
 (cl-defstruct erc-networks--id
   "Persistent identifying info for a network presence.
 
-Here, \"presence\" refers to some local state representing a persistent
-existence on a network.  The management of this state involves tracking
-associated buffers and what they're displaying.  Since a presence can
-outlast physical connections and survive changes in back-end transports
-\(and even outlive Emacs sessions), its identity must be resilient.
+Here, \"presence\" refers to some local state representing a client's
+existence on a network.  Some clients refer to this as a \"context\" or
+a \"net-id\".  The management of this state involves tracking associated
+buffers and what they're displaying.  Since a presence can outlast
+physical connections and survive changes in back-end transports (and
+even outlive Emacs sessions), its identity must be resilient.
 
 Essential to this notion of an enduring existence on a network is
 ensuring recovery from the loss of a server buffer.  Thus, any useful
@@ -776,28 +774,24 @@ set of connection parameters.  See the constructor
 (cl-defstruct (erc-networks--id-fixed
                (:include erc-networks--id)
                (:constructor erc-networks--id-fixed-create
-                             (given
-                              &aux
-                              (ts (float-time))
-                              (symbol given)))))
+                             (given &aux (ts (float-time)) (symbol given)))))
 
-(cl-defstruct (erc-networks--id-telescopic
+(cl-defstruct (erc-networks--id-eliding
                (:include erc-networks--id)
-               (:constructor erc-networks--id-telescopic-create
+               (:constructor erc-networks--id-eliding-create
                              (&aux
                               (ts (float-time))
-                              (parts (erc-networks--id-telescopic-init-parts))
-                              (symbol (erc-networks--id-telescopic-init-id
-                                       parts))
+                              (parts (erc-networks--id-eliding-init-parts))
+                              (symbol (erc-networks--id-eliding-init-id parts))
                               (len 1))))
-  "A network presence identified by certain connection parameters.
+  "A session context composed of hierarchical connection parameters.
 Two identifiers are considered equivalent when their non-empty `parts'
 slots compare equal.  Related identifiers share a common prefix of
 `parts' taken from connection parameters (given or discovered).  An
 identifier's unique `symbol', intended for display purposes, is created
 by concatenating the shortest common prefix among its relatives.  For
-example, related presences [b a r d o] and [b a z a r] would have IDs
-b/a/r and b/a/z respectively.  The separator is given by
+example, related presences [b a r d o] and [b a z a r] would have
+symbols b/a/r and b/a/z respectively.  The separator is given by
 `erc-networks--id-sep'."
   (parts nil :type sequence ; a vector of atoms
          :documentation "Sequence of identifying components.")
@@ -807,8 +801,8 @@ b/a/r and b/a/z respectively.  The separator is given by
 ;; For now, please use this instead of `erc-networks--id-fixed-p'.
 (cl-defgeneric erc-networks--id-given (net-id)
   "Return the preassigned identifier for a network presence, if any.
-This may have come in the form of an :id arg to an \"entry-point\"
-command like `erc-tls' or `erc'.")
+This may have originated from an `:id' arg to entry-point commands
+`erc-tls' or `erc'.")
 
 (cl-defmethod erc-networks--id-given ((_ erc-networks--id))
   nil)
@@ -830,13 +824,22 @@ command like `erc-tls' or `erc'.")
 
 ;; Otherwise, use an adaptive name derived from network params.
 (cl-defmethod erc-networks--id-create ((_ null))
-  (erc-networks--id-telescopic-create))
+  (erc-networks--id-eliding-create))
 
 ;; But honor an explicitly set `erc-rename-buffers' (compat).
 (cl-defmethod erc-networks--id-create
   ((_ null) &context (erc-obsolete-var erc-rename-buffers null))
   (erc-networks--id-fixed-create (intern (buffer-name))))
 
+;; But honor an explicitly set `erc-reuse-buffers' (compat).
+(cl-defmethod erc-networks--id-create
+  ((_ null) &context (erc-obsolete-var erc-reuse-buffers null))
+  (erc-networks--id-fixed-create (intern (buffer-name))))
+
+(cl-defmethod erc-networks--id-create
+  ((_ symbol) &context (erc-obsolete-var erc-reuse-buffers null))
+  (erc-networks--id-fixed-create (intern (buffer-name))))
+
 (cl-defgeneric erc-networks--id-on-connect (net-id)
   "Update NET-ID `erc-networks--id' after connection params known.
 This is typically during or just after MOTD.")
@@ -844,8 +847,8 @@ This is typically during or just after MOTD.")
 (cl-defmethod erc-networks--id-on-connect ((_ erc-networks--id))
   nil)
 
-(cl-defmethod erc-networks--id-on-connect ((id erc-networks--id-telescopic))
-  (erc-networks--id-telescopic-update id (erc-networks--id-telescopic-create)))
+(cl-defmethod erc-networks--id-on-connect ((id erc-networks--id-eliding))
+  (erc-networks--id-eliding-update id (erc-networks--id-eliding-create)))
 
 (cl-defgeneric erc-networks--id-equal-p (self other)
   "Return non-nil when two network identities exhibit underlying equality.
@@ -861,28 +864,28 @@ identities should be `equal' (timestamps aside) that 
aren't also `eq'.")
                                         (b erc-networks--id-fixed))
   (or (eq a b) (eq (erc-networks--id-symbol a) (erc-networks--id-symbol b))))
 
-(cl-defmethod erc-networks--id-equal-p ((a erc-networks--id-telescopic)
-                                        (b erc-networks--id-telescopic))
-  (or (eq a b) (equal (erc-networks--id-telescopic-parts a)
-                      (erc-networks--id-telescopic-parts b))))
+(cl-defmethod erc-networks--id-equal-p ((a erc-networks--id-eliding)
+                                        (b erc-networks--id-eliding))
+  (or (eq a b) (equal (erc-networks--id-eliding-parts a)
+                      (erc-networks--id-eliding-parts b))))
 
 ;; ERASE-ME: if some future extension were to come along offering
 ;; additional members, e.g., [Libera.Chat "bob" laptop], it'd likely
 ;; be cleaner to create a new struct type descending from
-;; `erc-networks--id-telescopic' than to convert this function into a
+;; `erc-networks--id-eliding' than to convert this function into a
 ;; generic.  However, the latter would be simpler because it'd just
 ;; require something like &context (erc-v3-device erc-v3--device-t).
 
-(defun erc-networks--id-telescopic-init-parts ()
+(defun erc-networks--id-eliding-init-parts ()
   "Return opaque list of atoms to serve as canonical identifier."
   (when-let ((network (erc-network))
              (nick (erc-current-nick)))
     (vector network (erc-downcase nick))))
 
 (defvar erc-networks--id-sep "/"
-  "Separator for joining `erc-networks--id-telescopic-parts' into a net ID.")
+  "Separator for joining `erc-networks--id-eliding-parts' into a net ID.")
 
-(defun erc-networks--id-telescopic-init-id (elts &optional len)
+(defun erc-networks--id-eliding-init-id (elts &optional len)
   "Create and return symbol to represent presence identified by ELTS.
 Use leading interval of length LEN as contributing components.  Combine
 them with string separator `erc-networks--id-sep'."
@@ -893,27 +896,27 @@ them with string separator `erc-networks--id-sep'."
                        (seq-subseq elts 0 len)
                        erc-networks--id-sep))))
 
-(defun erc-networks--id-telescopic-grow-id (nid)
+(defun erc-networks--id-eliding-grow-id (nid)
   "Grow NID by one component or return nil when at capacity."
-  (unless (= (length (erc-networks--id-telescopic-parts nid))
-             (erc-networks--id-telescopic-len nid))
+  (unless (= (length (erc-networks--id-eliding-parts nid))
+             (erc-networks--id-eliding-len nid))
     (setf (erc-networks--id-symbol nid)
-          (erc-networks--id-telescopic-init-id
-           (erc-networks--id-telescopic-parts nid)
-           (cl-incf (erc-networks--id-telescopic-len nid))))))
+          (erc-networks--id-eliding-init-id
+           (erc-networks--id-eliding-parts nid)
+           (cl-incf (erc-networks--id-eliding-len nid))))))
 
-(defun erc-networks--id-telescopic-reset-id (nid)
+(defun erc-networks--id-eliding-reset-id (nid)
   "Restore NID to its initial state."
-  (setf (erc-networks--id-telescopic-len nid) 1
+  (setf (erc-networks--id-eliding-len nid) 1
         (erc-networks--id-symbol nid)
-        (erc-networks--id-telescopic-init-id
-         (erc-networks--id-telescopic-parts nid))))
+        (erc-networks--id-eliding-init-id
+         (erc-networks--id-eliding-parts nid))))
 
-(defun erc-networks--id-telescopic-prefix-length (nid-a nid-b)
+(defun erc-networks--id-eliding-prefix-length (nid-a nid-b)
   "Return length of common initial prefix of NID-A and NID-B.
 Return nil when no such sequence exists (instead of zero)."
-  (when-let* ((a (erc-networks--id-telescopic-parts nid-a))
-              (b (erc-networks--id-telescopic-parts nid-b))
+  (when-let* ((a (erc-networks--id-eliding-parts nid-a))
+              (b (erc-networks--id-eliding-parts nid-b))
               (n (min (length a) (length b)))
               ((> n 0))
               ((equal (elt a 0) (elt b 0)))
@@ -924,22 +927,20 @@ Return nil when no such sequence exists (instead of 
zero)."
       (cl-incf i))
     i))
 
-(defun erc-networks--id-telescopic-update (dest source &rest overrides)
+(defun erc-networks--id-eliding-update (dest source &rest overrides)
   "Update DEST from SOURCE in place.
 Copy slots into DEST from SOURCE and recompute ID.  Both SOURCE and DEST
 must be `erc-networks--id' objects.  OVERRIDES is an optional plist of SLOT VAL
 pairs."
-  (setf (erc-networks--id-telescopic-parts dest)
-        (or (plist-get overrides :parts)
-            (erc-networks--id-telescopic-parts source))
-        (erc-networks--id-telescopic-len dest)
-        (or (plist-get overrides :len)
-            (erc-networks--id-telescopic-len source))
+  (setf (erc-networks--id-eliding-parts dest)
+        (or (plist-get overrides :parts) (erc-networks--id-eliding-parts 
source))
+        (erc-networks--id-eliding-len dest)
+        (or (plist-get overrides :len) (erc-networks--id-eliding-len source))
         (erc-networks--id-symbol dest)
         (or (plist-get overrides :symbol)
-            (erc-networks--id-telescopic-init-id
-             (erc-networks--id-telescopic-parts dest)
-             (erc-networks--id-telescopic-len dest)))))
+            (erc-networks--id-eliding-init-id
+             (erc-networks--id-eliding-parts dest)
+             (erc-networks--id-eliding-len dest)))))
 
 (cl-defgeneric erc-networks--id-reload (_nid &optional _proc _parsed)
   "Handle an update to the current network identity.
@@ -947,12 +948,11 @@ If provided, PROC should be the current 
`erc-server-process' and PARSED
 the current `erc-response'.  NID is an `erc-networks--id' object."
   nil)
 
-(cl-defmethod erc-networks--id-reload ((nid erc-networks--id-telescopic)
+(cl-defmethod erc-networks--id-reload ((nid erc-networks--id-eliding)
                                        &optional proc parsed)
-  "Refresh identity after an `erc-networks--id-telescopic-parts' update."
-  (erc-networks--id-telescopic-update nid (erc-networks--id-telescopic-create)
-                                      :len
-                                      (erc-networks--id-telescopic-len nid))
+  "Refresh identity after an `erc-networks--id-eliding-parts' update."
+  (erc-networks--id-eliding-update nid (erc-networks--id-eliding-create)
+                                   :len (erc-networks--id-eliding-len nid))
   (erc-networks--rename-server-buffer (or proc erc-server-process) parsed)
   (erc-networks--shrink-ids-and-buffer-names-any)
   (erc-with-all-buffers-of-server
@@ -970,16 +970,16 @@ the current `erc-response'.  NID is an `erc-networks--id' 
object."
   nil)
 
 (cl-defmethod erc-networks--id-ensure-comparable
-  ((nid erc-networks--id-telescopic) (other erc-networks--id-telescopic))
+  ((nid erc-networks--id-eliding) (other erc-networks--id-eliding))
   "Grow NID along with that of the current buffer.
 Rename the current buffer if its NID has grown."
-  (when-let ((n (erc-networks--id-telescopic-prefix-length other nid)))
-    (while (and (<= (erc-networks--id-telescopic-len nid) n)
-                (erc-networks--id-telescopic-grow-id nid)))
+  (when-let ((n (erc-networks--id-eliding-prefix-length other nid)))
+    (while (and (<= (erc-networks--id-eliding-len nid) n)
+                (erc-networks--id-eliding-grow-id nid)))
     ;; Grow and rename a visited buffer and all its targets
-    (when (and (> (erc-networks--id-telescopic-len nid)
-                  (erc-networks--id-telescopic-len other))
-               (erc-networks--id-telescopic-grow-id other))
+    (when (and (> (erc-networks--id-eliding-len nid)
+                  (erc-networks--id-eliding-len other))
+               (erc-networks--id-eliding-grow-id other))
       ;; Rename NID's buffers using current ID
       (erc-buffer-filter (lambda ()
                            (when (eq erc-networks--id other)
@@ -996,7 +996,7 @@ Rename the current buffer if its NID has grown."
 ;;;; Buffer association
 
 (cl-defgeneric erc-networks--shrink-ids-and-buffer-names ()
-  nil) ; concrete default implementation for non-telescopic IDs
+  nil) ; concrete default implementation for non-eliding IDs
 
 (defun erc-networks--refresh-buffer-names (identity &optional omit)
   "Ensure all colliding buffers for network IDENTITY have suffixes.
@@ -1021,23 +1021,23 @@ when determining collisions."
     (erc-buffer-filter
      (lambda ()
        (when (and erc-networks--id
-                  (erc-networks--id-telescopic-p erc-networks--id)
+                  (erc-networks--id-eliding-p erc-networks--id)
                   (not (memq (current-buffer) omit))
                   (not (memq erc-networks--id grown))
-                  (> (erc-networks--id-telescopic-len erc-networks--id) 1))
+                  (> (erc-networks--id-eliding-len erc-networks--id) 1))
          (push erc-networks--id grown))))
     ;; Check for other identities with shared prefix.  If none exists,
-    ;; and identity is overlong, shrink it.
+    ;; and an identity is overlong, shrink it.
     (dolist (nid grown)
       (let ((skip (not (null omit))))
         (catch 'found
           (dolist (other grown)
             (unless (eq nid other)
               (setq skip nil)
-              (when (erc-networks--id-telescopic-prefix-length nid other)
+              (when (erc-networks--id-eliding-prefix-length nid other)
                 (throw 'found (setq skip t))))))
-        (unless (or skip (< (erc-networks--id-telescopic-len nid) 2))
-          (erc-networks--id-telescopic-reset-id nid)
+        (unless (or skip (< (erc-networks--id-eliding-len nid) 2))
+          (erc-networks--id-eliding-reset-id nid)
           (erc-buffer-filter
            (lambda ()
              (when (and (eq erc-networks--id nid)
@@ -1047,7 +1047,7 @@ when determining collisions."
                  (erc-networks--maybe-update-buffer-name))))))))))
 
 (cl-defmethod erc-networks--shrink-ids-and-buffer-names
-  (&context (erc-networks--id erc-networks--id-telescopic))
+  (&context (erc-networks--id erc-networks--id-eliding))
   (erc-networks--shrink-ids-and-buffer-names-any (current-buffer)))
 
 (defun erc-networks-rename-surviving-target-buffer ()
@@ -1055,7 +1055,9 @@ when determining collisions."
 But only do so when there's a single survivor with a target matching
 that of the dying buffer."
   (when-let*
-      ((target erc--target)
+      (((with-suppressed-warnings ((obsolete erc-reuse-buffers))
+          erc-reuse-buffers))
+       (target erc--target)
        ;; Buffer name includes ID suffix
        ((not (string= (erc--target-symbol target) ; string= t "t" -> t
                       (erc-downcase (buffer-name)))))
@@ -1073,7 +1075,8 @@ that of the dying buffer."
 
 (defun erc-networks-shrink-ids-and-buffer-names ()
   "Recompute network IDs and buffer names, ignoring the current buffer.
-Only do so when an IRC connection's context supports qualified naming."
+Only do so when an IRC connection's context supports qualified naming.
+Do not discriminate based on whether a buffer's connection is active."
   (erc-networks--shrink-ids-and-buffer-names))
 
 (defun erc-networks--examine-targets (identity target on-dupe on-collision)
@@ -1086,9 +1089,8 @@ object."
   (let ((announced erc-server-announced-name))
     (erc-buffer-filter
      (lambda ()
-       (when (and erc--target
-                  (eq (erc--target-symbol erc--target)
-                      (erc--target-symbol target)))
+       (when (and erc--target (eq (erc--target-symbol erc--target)
+                                  (erc--target-symbol target)))
          (let ((oursp (if (erc--target-channel-local-p target)
                           (equal announced erc-server-announced-name)
                         (erc-networks--id-equal-p identity erc-networks--id))))
@@ -1100,10 +1102,17 @@ object."
 (defun erc-networks--construct-target-buffer-name (target)
   "Return TARGET@suffix."
   (concat (erc--target-string target)
-          erc-networks--qualified-sep
-          (if (erc--target-channel-local-p target)
-              erc-server-announced-name
-            (symbol-name (erc-networks--id-symbol erc-networks--id)))))
+          (if (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+                erc-reuse-buffers)
+              erc-networks--qualified-sep "/")
+          (cond
+           ((not (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+                   erc-reuse-buffers))
+            (cadr (split-string
+                   (symbol-name (erc-networks--id-symbol erc-networks--id))
+                   "/")))
+           ((erc--target-channel-local-p target) erc-server-announced-name)
+           (t (symbol-name (erc-networks--id-symbol erc-networks--id))))))
 
 (defun erc-networks--ensure-qual-target-buffer-name ()
   (when-let* ((new-name (erc-networks--construct-target-buffer-name
@@ -1145,7 +1154,9 @@ suffixes going from newest to oldest."
                         (erc-networks--ensure-qual-target-buffer-name)
                         t)))
          ;; Must follow ^ because NID may have been modified
-         (name (if namesakes
+         (name (if (or namesakes (not (with-suppressed-warnings
+                                          ((obsolete erc-reuse-buffers))
+                                        erc-reuse-buffers)))
                    (erc-networks--construct-target-buffer-name target)
                  (erc--target-string target)))
          placeholder)
@@ -1159,6 +1170,10 @@ suffixes going from newest to oldest."
         (rename-buffer temp-name)
         (setq placeholder (get-buffer-create name))
         (rename-buffer name 'unique)))
+    (unless (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+              erc-reuse-buffers)
+      (when (string-suffix-p ">" name)
+        (setq name (substring name 0 -3))))
     (dolist (ex (erc-networks--id-sort-buffers existing))
       (with-current-buffer ex
         (rename-buffer name 'unique)))
@@ -1168,6 +1183,7 @@ suffixes going from newest to oldest."
 
 ;; Functions:
 
+;;;###autoload
 (defun erc-determine-network ()
   "Return the name of the network or \"Unknown\" as a symbol.
 Use the server parameter NETWORK if provided, otherwise parse the
@@ -1222,7 +1238,6 @@ by the `RPL_ISUPPORT' NETWORK parameter."
 (defun erc-networks--set-name (_proc parsed)
   "Set `erc-network' to the value returned by `erc-networks--determine'.
 Signal an error when the network cannot be determined."
-  (cl-assert (not erc-server-connected))
   ;; Always update (possibly clobber) current value, if any.
   (let ((name (erc-networks--determine)))
     (when (eq name erc-networks--name-missing-sentinel)
@@ -1245,20 +1260,16 @@ Signal an error when the network cannot be determined."
 Copy source (prefix) from MOTD-ish message as a last resort."
   ;; The 004 handler never ran; see 2004-03-10 Diane Murray in change log
   (unless erc-server-announced-name
-    (let ((m (concat "Failed to determine server name. "
-                     "If this was unexpected, please M-x erc-bug RET.")))
-      (erc-display-error-notice parsed m))
+    (erc-display-error-notice parsed "Failed to determine server name.")
+    (erc-display-error-notice
+     parsed (concat "If this was unexpected, consider reporting it via "
+                    (substitute-command-keys "\\[erc-bug]") "."))
     (setq erc-server-announced-name (erc-response.sender parsed)))
   nil)
 
-(defun erc-networks--copy-name (_buffer)
-  "Copy `erc-network' from the server buffer."
-  ;; Arg _buffer is always current buffer.
-  (when erc--target
-    (setq erc-network (erc-network))))
-
 (defun erc-unset-network-name (_nick _ip _reason)
   "Set `erc-network' to nil."
+  (declare (obsolete "`erc-network' is now effectively read-only" "29.1"))
   (setq erc-network nil)
   nil)
 
@@ -1332,8 +1343,8 @@ Must be called from the replacement buffer."
 
 If a dupe is found, adopt its identity by overwriting ours.  Otherwise,
 take steps to ensure it can effectively be compared to ours, now and
-into the future.  Note target buffers are considered as well because
-server buffers are often killed."
+into the future.  Note that target buffers are considered as well
+because server buffers are often killed."
   (let* ((identity erc-networks--id)
          (buffer (current-buffer))
          (f (lambda ()
@@ -1401,32 +1412,43 @@ actual renaming."
                                                  name))
            (erc-set-active-buffer existing))
           ;; Copy over old buffer's contents and kill it
-          (erc-reuse-buffers
+          ((with-suppressed-warnings ((obsolete erc-reuse-buffers))
+             erc-reuse-buffers)
            (erc-networks--copy-over-server-buffer-contents existing name)
            (rename-buffer name))
           (t (rename-buffer (generate-new-buffer-name name)))))
   nil)
 
+;; Soju v0.4.0 only sends ISUPPORT on upstream reconnect, so this
+;; doesn't apply.  ZNC 1.8.2, however, still sends the entire burst.
+(defconst erc-networks--bouncer-targets '(*status bouncerserv)
+  "Case-mapped symbols matching known bouncer service-bot targets.")
+
+(defun erc-networks-on-MOTD-end (proc parsed)
+  "Call on-connect functions with server PROC and PARSED message.
+This must run before `erc-server-connected' is set."
+  (when erc-server-connected
+    (unless (erc-buffer-filter (lambda ()
+                                 (and erc--target
+                                      (memq (erc--target-symbol erc--target)
+                                            erc-networks--bouncer-targets)))
+                               proc)
+      (let ((m (concat "Unexpected state detected. Please report via "
+                       (substitute-command-keys "\\[erc-bug]") ".")))
+        (erc-display-error-notice parsed m))))
+
+  ;; For now, retain compatibility with erc-server-NNN-functions.
+  (or (erc-networks--ensure-announced proc parsed)
+      (erc-networks--set-name proc parsed)
+      (erc-networks--init-identity proc parsed)
+      (erc-networks--rename-server-buffer proc parsed)))
+
 (define-erc-module networks nil
   "Provide data about IRC networks."
-  ((add-hook 'erc-server-376-functions #'erc-networks--rename-server-buffer)
-   (add-hook 'erc-server-422-functions #'erc-networks--rename-server-buffer)
-   (add-hook 'erc-server-376-functions #'erc-networks--init-identity)
-   (add-hook 'erc-server-422-functions #'erc-networks--init-identity)
-   (add-hook 'erc-server-376-functions #'erc-networks--set-name)
-   (add-hook 'erc-server-422-functions #'erc-networks--set-name)
-   (add-hook 'erc-server-376-functions #'erc-networks--ensure-announced)
-   (add-hook 'erc-server-422-functions #'erc-networks--ensure-announced)
-   (add-hook 'erc-connect-pre-hook #'erc-networks--copy-name))
-  ((remove-hook 'erc-server-376-functions #'erc-networks--ensure-announced)
-   (remove-hook 'erc-server-422-functions #'erc-networks--ensure-announced)
-   (remove-hook 'erc-server-376-functions #'erc-networks--set-name)
-   (remove-hook 'erc-server-422-functions #'erc-networks--set-name)
-   (remove-hook 'erc-server-376-functions #'erc-networks--init-identity)
-   (remove-hook 'erc-server-422-functions #'erc-networks--init-identity)
-   (remove-hook 'erc-server-376-functions #'erc-networks--rename-server-buffer)
-   (remove-hook 'erc-server-422-functions #'erc-networks--rename-server-buffer)
-   (remove-hook 'erc-connect-pre-hook #'erc-networks--copy-name)))
+  ((add-hook 'erc-server-376-functions #'erc-networks-on-MOTD-end)
+   (add-hook 'erc-server-422-functions #'erc-networks-on-MOTD-end))
+  ((remove-hook 'erc-server-376-functions #'erc-networks-on-MOTD-end)
+   (remove-hook 'erc-server-422-functions #'erc-networks-on-MOTD-end)))
 
 (defun erc-ports-list (ports)
   "Return a list of PORTS.
diff --git a/lisp/erc/erc-services.el b/lisp/erc/erc-services.el
index ca3eca5bdb..c43fac2f0a 100644
--- a/lisp/erc/erc-services.el
+++ b/lisp/erc/erc-services.el
@@ -174,15 +174,15 @@ function `erc-nickserv-get-password'."
   :version "28.1"
   :type 'boolean)
 
-(defcustom erc-auth-source-parameters-services-function
-  #'erc-auth-source-determine-params-merge
-  "NickServ-specific filter for `auth-source-search'.
-Called with keyword parameters known to `auth-source-search' and
-relevant to authenticating to nickname services.  In return, ERC expects
-a possibly modified set of parameters for querying auth-source and for
-narrowing the results.  See info node `(erc) Connecting' for details."
+(defcustom erc-auth-source-services-function #'erc-auth-source-search
+  "Function to retrieve NickServ password from auth-source.
+Called with a subset of keyword parameters known to `auth-source-search'
+and relevant to authenticating to nickname services.  In return, ERC
+expects a string to send as the password, or nil, to fall through to the
+next method, such as prompting.  See info node `(erc) Connecting' for
+details."
   :package-version '(ERC . "5.4.1") ; FIXME update when publishing to ELPA
-  :type '(choice (const erc-auth-source-determine-params-merge)
+  :type '(choice (const erc-auth-source-search)
                  (const nil)
                  function))
 
@@ -444,19 +444,17 @@ lookups stops and this function returns it (or returns 
nil if it
 is empty).  Otherwise, no corresponding password was found, and
 it returns nil."
   (when-let*
-      ((esid (erc-networks--id-symbol erc-networks--id))
+      ((nid (erc-networks--id-symbol erc-networks--id))
        (ret (or (when erc-nickserv-passwords
                   (assoc-default nick
-                                 (cadr (assq esid erc-nickserv-passwords))))
+                                 (cadr (assq nid erc-nickserv-passwords))))
                 (when (and erc-use-auth-source-for-nickserv-password
-                           erc-auth-source-parameters-services-function)
-                  (apply #'erc--auth-source-search
-                         (funcall erc-auth-source-parameters-services-function
-                                  :user nick)))
+                           erc-auth-source-services-function)
+                  (funcall erc-auth-source-services-function :user nick))
                 (when erc-prompt-for-nickserv-password
                   (read-passwd
                    (format "NickServ password for %s on %s (RET to cancel): "
-                           nick esid)))))
+                           nick nid)))))
        ((not (string-empty-p ret))))
     ret))
 
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index e8ee7b3710..c2b7dacf84 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -134,6 +134,7 @@
 (defvar erc--server-last-reconnect-count)
 (defvar erc--server-reconnecting)
 (defvar erc-channel-members-changed-hook)
+(defvar erc-network)
 (defvar erc-networks--id)
 (defvar erc-server-367-functions)
 (defvar erc-server-announced-name)
@@ -1675,6 +1676,14 @@ effect when `erc-join-buffer' is set to `frame'."
            (erc-channel-p (erc-default-target))))
         (t nil)))
 
+;; For the sake of compatibility, a historical quirk concerning this
+;; option, when nil, has been preserved: all buffers are suffixed with
+;; the original dialed host name, which is usually something like
+;; irc.libera.chat.  Collisions are handled by adding a uniquifying
+;; numeric suffix of the form <N>.  Note that channel reassociation
+;; behavior involving this option (when nil) was inverted in 28.1 (ERC
+;; 5.4 and 5.4.1).  This was regrettable and has since been undone.
+
 (defcustom erc-reuse-buffers t
   "If nil, create new buffers on joining a channel/query.
 If non-nil, a new buffer will only be created when you join
@@ -1684,6 +1693,9 @@ the existing buffers will be reused."
   :group 'erc-buffers
   :type 'boolean)
 
+(make-obsolete-variable 'erc-reuse-buffers
+                        "old behavior when t now permanent" "29.1")
+
 (defun erc-normalize-port (port)
   "Normalize the port specification PORT to integer form.
 PORT may be an integer, a string or a symbol.  If it is a string or a
@@ -1734,8 +1746,7 @@ param TARGET (retained for compatibility).  Whenever 
possibly, prefer
 returning TGT-INFO's string unmodified.  But when a case-insensitive
 collision prevents that, return target@ID when ID is non-nil or
 target@network otherwise after renaming the conflicting buffer in the
-same manner.  If the `networks' module isn't loaded, return target or
-target<n>."
+same manner."
   (when target ; compat
     (setq tgt-info (erc--target-from-string target)))
   (if tgt-info
@@ -1744,24 +1755,34 @@ target<n>."
                        (erc-networks--reconcile-buffer-names tgt-info
                                                              erc-networks--id)
                      (erc--target-string tgt-info))))
-        (if (and esid erc-reuse-buffers)
+        (if (and esid (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+                        erc-reuse-buffers))
             name
           (generate-new-buffer-name name)))
-    (if id
+    (if (and (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+               erc-reuse-buffers)
+             id)
         (progn
           (when-let* ((buf (get-buffer (symbol-name id)))
                       ((erc-server-process-alive buf)))
             (user-error  "Session with ID %S already exists" id))
           (symbol-name id))
       (generate-new-buffer-name (if (and server port)
-                                    (format "%s:%s" server port)
+                                    (if (with-suppressed-warnings
+                                            ((obsolete erc-reuse-buffers))
+                                          erc-reuse-buffers)
+                                        (format "%s:%s" server port)
+                                      (format "%s:%s/%s" server port server))
                                   (or server port))))))
 
 (defun erc-get-buffer-create (server port target &optional tgt-info id)
   "Create a new buffer based on the arguments."
   (when target ; compat
     (setq tgt-info (erc--target-from-string target)))
-  (if (and erc--server-reconnecting (not tgt-info))
+  (if (and erc--server-reconnecting
+           (not tgt-info)
+           (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+             erc-reuse-buffers))
       (current-buffer)
     (get-buffer-create
      (erc-generate-new-buffer-name server port nil tgt-info id))))
@@ -1927,6 +1948,9 @@ all channel buffers on all servers."
 ;; (QUERY . bob) to serve as the list's head, was either never fully
 ;; integrated or was partially clobbered prior to the introduction of
 ;; version control.  But vestiges remain (see `erc-dcc-chat-mode').
+;; And despite appearances, no evidence has emerged that ERC ever
+;; supported one-to-many target buffers.  If such a thing was aspired
+;; to, it was never realized.
 ;;
 ;; New library code should use the `erc--target' struct instead.
 ;; Third-party code can continue to use this until a getter for
@@ -2127,7 +2151,10 @@ Returns the buffer for the given server or channel."
          (buffer (erc-get-buffer-create server port nil target id))
          (old-buffer (current-buffer))
          old-point
-         (continued-session erc--server-reconnecting))
+         (continued-session (and erc--server-reconnecting
+                                 (with-suppressed-warnings
+                                     ((obsolete erc-reuse-buffers))
+                                   erc-reuse-buffers))))
     (when connect (run-hook-with-args 'erc-before-connect server port nick))
     (erc-update-modules)
     (set-buffer buffer)
@@ -2157,7 +2184,9 @@ Returns the buffer for the given server or channel."
     (set-marker erc-insert-marker (point))
     ;; stack of default recipients
     (setq erc-default-recipients tgt-list)
-    (setq erc--target target)
+    (when target
+      (setq erc--target target
+            erc-network (erc-network)))
     (setq erc-server-current-nick nil)
     ;; Initialize erc-server-users and erc-channel-users
     (if connect
@@ -3319,34 +3348,32 @@ For a list of user commands (/join /part, ...):
 (defalias 'erc-cmd-H #'erc-cmd-HELP)
 (put 'erc-cmd-HELP 'process-not-needed t)
 
-(defcustom erc-auth-source-parameters-server-function
-  #'erc-auth-source-determine-params-merge
-  "Server-password filter for `auth-source-search'.
-Called with keyword parameters known to `auth-source-search' and
-relevant to an opening \"PASS\" command, if any.  In return, ERC expects
-an optionally modified set of parameters to use both for the auth-source
-query and for narrowing its results.  A value of nil tells ERC to omit
-the \"PASS\" command completely.  An explicit `:password' argument to
-entry-point commands `erc' and `erc-tls' also inhibits look-up.  See
-info node `(erc) Connecting' for details."
+(defcustom erc-auth-source-server-function #'erc-auth-source-search
+  "Function to query auth-source for a server password.
+Called with a subset of keyword parameters known to `auth-source-search'
+and relevant to an opening \"PASS\" command, if any.  In return, ERC
+expects a string to send as the server password, or nil, to skip the
+\"PASS\" command completely.  An explicit `:password' argument to
+entry-point commands `erc' and `erc-tls' also inhibits lookup, as does
+setting this option to nil.  See info node `(erc) Connecting' for
+details."
   :package-version '(ERC . "5.4.1") ; FIXME update when publishing to ELPA
   :group 'erc
-  :type '(choice (const erc-auth-source-determine-params-merge)
+  :type '(choice (const erc-auth-source-search)
                  (const nil)
                  function))
 
-(defcustom erc-auth-source-parameters-join-function
-  #'erc-auth-source-determine-params-merge
-  "Channel-join filter for `auth-source-search'.
-Called with keyword parameters known to `auth-source-search' and
-relevant to joining a password-protected channel.  In return, ERC
-expects an optionally modified set of parameters to use both for the
-auth-source query and for narrowing its results.  A value of nil tells
-ERC to forgo consulting auth-source for channel keys.  For more
-information, see info node `(erc) Connecting'."
+(defcustom erc-auth-source-join-function #'erc-auth-source-search
+  "Function to query auth-source on joining a channel.
+Called with a subset of keyword arguments known to `auth-source-search'
+and relevant to joining a password-protected channel.  In return, ERC
+expects a string to use as the channel \"key\", or nil to just join the
+channel normally.  Setting the option itself to nil tells ERC to always
+forgo consulting auth-source for channel keys.  For more information,
+see info node `(erc) Connecting'."
   :package-version '(ERC . "5.4.1") ; FIXME update when publishing to ELPA
   :group 'erc
-  :type '(choice (const erc-auth-source-determine-params-merge)
+  :type '(choice (const erc-auth-source-search)
                  (const nil)
                  function))
 
@@ -3367,7 +3394,7 @@ information, see info node `(erc) Connecting'."
           (cons :port (delq nil ports))
           (cons :require '(:secret)))))
 
-(defun erc-auth-source-determine-params-merge (&rest plist)
+(defun erc--auth-source-determine-params-merge (&rest plist)
   "Return a plist of merged keyword args to pass to `auth-source-search'.
 Combine items in PLIST with others derived from the current connection
 context, but prioritize the former.  For keys not present in PLIST,
@@ -3385,8 +3412,25 @@ the announced name in both cases."
 
 (defun erc--auth-source-search (&rest defaults)
   "Ask auth-source for a secret and return it if found.
-Use DEFAULTS as arguments for querying auth-source and as a guide for
-narrowing the results.  Return a string if found or nil otherwise."
+Use DEFAULTS as keyword arguments for querying auth-source and as a
+guide for narrowing results.  Return a string if found or nil otherwise.
+The ordering of DEFAULTS influences how results are filtered, as does
+the ordering of the members of any individual composite values.  If
+necessary, the former takes priority.  For example, if DEFAULTS were to
+contain
+
+  :host (\"foo\" \"bar\") :port (\"123\" \"456\")
+
+the secret from an auth-source entry of host foo and port 456 would be
+chosen over another of host bar and port 123.  However, if DEFAULTS
+looked like
+
+  :port (\"123\" \"456\") :host (\"foo\" \"bar\")
+
+the opposite would be true.  In both cases, two entries with the same
+host but different ports would result in the one with port 123 getting
+the nod.  Much the same would happen for entries sharing only a port:
+the one with host foo would win."
   (when-let*
       ((priority (map-keys defaults))
        (test (lambda (a b)
@@ -3408,19 +3452,26 @@ narrowing the results.  Return a string if found or nil 
otherwise."
                 (secret (plist-get (car sorted) :secret)))
       (if (functionp secret) (funcall secret) secret))))
 
+(defun erc-auth-source-search (&rest plist)
+  "Call `auth-source-search', possibly with keyword params in PLIST."
+  ;; These exist as separate helpers in case folks should find them
+  ;; useful.  If that's you, please request that they be exported.
+  (apply #'erc--auth-source-search
+         (apply #'erc--auth-source-determine-params-merge plist)))
+
 (defun erc-server-join-channel (server channel &optional secret)
   "Join CHANNEL, optionally with SECRET.
-Without SECRET, consult auth source, using SERVER if non-nil."
-  (unless (or secret (not erc-auth-source-parameters-join-function))
+Without SECRET, consult auth-source, possibly passing SERVER as the
+`:host' query parameter."
+  (unless (or secret (not erc-auth-source-join-function))
     (unless server
       (when (and erc-server-announced-name
                  (erc--valid-local-channel-p channel))
         (setq server erc-server-announced-name)))
-    (let ((args (apply erc-auth-source-parameters-join-function
-                       `(,@(and server (list :host server)) :user channel))))
-      (setq secret (apply #'erc--auth-source-search args))))
+    (setq secret (apply erc-auth-source-join-function
+                        `(,@(and server (list :host server)) :user ,channel))))
   (erc-log (format "cmd: JOIN: %s" channel))
-  (erc-server-send (concat "JOIN " channel (when secret (concat " " secret)))))
+  (erc-server-send (concat "JOIN " channel (and secret (concat " " secret)))))
 
 (defun erc--valid-local-channel-p (channel)
   "Non-nil when channel is server-local on a network that allows them."
@@ -3863,12 +3914,7 @@ If S is non-nil, it will be used as the quit reason."
   "Zippy quit message.
 
 If S is non-nil, it will be used as the quit reason."
-  (or s
-      (if (fboundp 'yow)
-          (if (>= emacs-major-version 28)
-              (string-replace "\n" "" (yow))
-            (replace-regexp-in-string "\n" "" (yow)))
-        (erc-quit/part-reason-default))))
+  (or s (erc-quit/part-reason-default)))
 
 (make-obsolete 'erc-quit-reason-zippy "it will be removed." "24.4")
 
@@ -3892,12 +3938,7 @@ If S is non-nil, it will be used as the part reason."
   "Zippy part message.
 
 If S is non-nil, it will be used as the quit reason."
-  (or s
-      (if (fboundp 'yow)
-          (if (>= emacs-major-version 28)
-              (string-replace "\n" "" (yow))
-            (replace-regexp-in-string "\n" "" (yow)))
-        (erc-quit/part-reason-default))))
+  (or s (erc-quit/part-reason-default)))
 
 (make-obsolete 'erc-part-reason-zippy "it will be removed." "24.4")
 
@@ -3986,9 +4027,13 @@ the message given by REASON."
       (when process
         (delete-process process))
       (erc-server-reconnect)
-      (with-suppressed-warnings ((obsolete erc-server-reconnecting))
-        (setq erc-server-reconnecting nil))
-      (setq erc--server-reconnecting nil)))
+      (with-suppressed-warnings ((obsolete erc-server-reconnecting)
+                                 ((obsolete erc-reuse-buffers)))
+        (if erc-reuse-buffers
+            (progn (cl-assert (not erc--server-reconnecting))
+                   (cl-assert (not erc-server-reconnecting)))
+          (setq erc--server-reconnecting nil
+                erc-server-reconnecting nil)))))
   t)
 (put 'erc-cmd-RECONNECT 'process-not-needed t)
 
@@ -6464,13 +6509,10 @@ non-nil value is found.
 
 (defun erc--compute-server-password (password nick)
   "Maybe provide a PASSWORD argument for the IRC \"PASS\" command.
-Use NICK for the user field when querying auth-source.  Defer to
-`erc-auth-source-parameters-server-function' for the host param."
-  (or password
-      (and erc-auth-source-parameters-server-function
-           (apply #'erc--auth-source-search
-                  (funcall erc-auth-source-parameters-server-function
-                           :user nick)))))
+When `erc-auth-source-server-function' is non-nil, call it with NICK for
+the user field and use whatever it returns as the server password."
+  (or password (and erc-auth-source-server-function
+                    (funcall erc-auth-source-server-function :user nick))))
 
 (defun erc-compute-full-name (&optional full-name)
   "Return user's full name.
diff --git a/test/lisp/erc/erc-dcc-tests.el b/test/lisp/erc/erc-dcc-tests.el
new file mode 100644
index 0000000000..a1dfbab9dc
--- /dev/null
+++ b/test/lisp/erc/erc-dcc-tests.el
@@ -0,0 +1,167 @@
+;;; erc-dcc-tests.el --- Tests for erc-dcc  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022 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:
+
+;;; Code:
+(require 'ert)
+(require 'erc-dcc)
+
+(ert-deftest erc-dcc-ctcp-query-send-regexp ()
+  (let ((s "DCC SEND \"file name\" 2130706433 9899 1405135128"))
+    (should (string-match erc-dcc-ctcp-query-send-regexp s))
+    (should-not (match-string 2 s))
+    (should (string= "file name" (match-string 1 s)))
+    (should (string= "SEND" (match-string 6 s))))
+  (let ((s "DCC SEND \"file \\\" name\" 2130706433 9899 1405135128"))
+    (should (string-match erc-dcc-ctcp-query-send-regexp s))
+    (should-not (match-string 2 s))
+    (should (string= "SEND" (match-string 6 s)))
+    (should (string= "file \" name"
+                     (erc-dcc-unquote-filename (match-string 1 s)))))
+  (let ((s "DCC SEND filename 2130706433 9899 1405135128"))
+    (should (string-match erc-dcc-ctcp-query-send-regexp s))
+    (should (string= "filename" (match-string 2 s)))
+    (should (string= "2130706433" (match-string 3 s)))
+    (should (string= "9899" (match-string 4 s)))
+    (should (string= "1405135128" (match-string 5 s))))
+  (let ((s "DCC TSEND filename 2130706433 9899 1405135128"))
+    (should (string-match erc-dcc-ctcp-query-send-regexp s))
+    (should (string= "TSEND" (match-string 6 s)))))
+
+;; This also indirectly tests base functionality for
+;; `erc-dcc-do-LIST-command'
+
+(defun erc-dcc-tests--dcc-handle-ctcp-send (turbo)
+  (let (erc-send-completed-hook
+        erc-insert-modify-hook
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
+    (with-current-buffer (get-buffer-create "fake-server")
+      (erc-mode)
+      (setq erc-server-process
+            (start-process "fake" (current-buffer) "sleep" "10")
+            erc-input-marker (make-marker)
+            erc-insert-marker (make-marker)
+            erc-server-current-nick "dummy")
+      (set-process-query-on-exit-flag erc-server-process nil)
+      (should-not erc-dcc-list)
+      (erc-ctcp-query-DCC erc-server-process
+                          "tester"
+                          "~tester"
+                          "fake.irc"
+                          "dummy"
+                          (concat "DCC " (if turbo "TSEND" "SEND")
+                                  " foo 2130706433 9899 1405135128"))
+      (should-not (cdr erc-dcc-list))
+      (should (equal (plist-put (car erc-dcc-list) :parent 'fake)
+                     `(:nick "tester!~tester@fake.irc"
+                             :type GET
+                             :peer nil
+                             :parent fake
+                             :ip "127.0.0.1"
+                             :port "9899"
+                             :file "foo"
+                             :size 1405135128
+                             :turbo ,(and turbo t)
+                             :secure nil)))
+      (goto-char (point-min))
+      (should (search-forward "file foo offered by tester" nil t))
+      (erc-dcc-do-LIST-command erc-server-process)
+      (should (search-forward-regexp (concat
+                                      "GET +no +1405135128 +foo"
+                                      (and turbo " +(T)") "$")
+                                     nil t))
+      (when noninteractive
+        (kill-buffer))))
+  ;; `erc-dcc-list' is global; must leave it empty
+  (should erc-dcc-list)
+  (setq erc-dcc-list nil))
+
+(ert-deftest erc-dcc-handle-ctcp-send--base ()
+  (erc-dcc-tests--dcc-handle-ctcp-send nil))
+
+(ert-deftest erc-dcc-handle-ctcp-send--turbo ()
+  (erc-dcc-tests--dcc-handle-ctcp-send t))
+
+(ert-deftest erc-dcc-do-GET-command ()
+  (with-temp-buffer
+    (let* ((proc (start-process "fake" (current-buffer) "sleep" "10"))
+           (elt (list :nick "tester!~tester@fake.irc"
+                      :type 'GET
+                      :peer nil
+                      :parent proc
+                      :ip "127.0.0.1"
+                      :port "9899"
+                      :file "foo.bin"
+                      :size 1405135128))
+           (erc-dcc-list (list elt))
+           ;;
+           erc-accidental-paste-threshold-seconds
+           erc-insert-modify-hook erc-send-completed-hook
+           erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook
+           calls)
+      (erc-mode)
+      (setq erc-server-process proc
+            erc-input-marker (make-marker)
+            erc-insert-marker (make-marker)
+            erc-server-current-nick "dummy")
+      (set-process-query-on-exit-flag proc nil)
+      (cl-letf (((symbol-function 'read-file-name)
+                 (lambda (&rest _) "foo.bin"))
+                ((symbol-function 'erc-dcc-get-file)
+                 (lambda (&rest r) (push r calls))))
+        (goto-char (point-max))
+        (set-marker erc-insert-marker (point-max))
+        (erc-display-prompt)
+
+        (ert-info ("No turbo")
+          (should-not (plist-member elt :turbo))
+          (goto-char erc-input-marker)
+          (insert "/dcc GET tester foo.bin")
+          (erc-send-current-line)
+          (should-not (plist-member (car erc-dcc-list) :turbo))
+          (should (equal (pop calls) (list elt "foo.bin" proc))))
+
+        (ert-info ("Arg turbo in pos 2")
+          (should-not (plist-member elt :turbo))
+          (goto-char erc-input-marker)
+          (insert "/dcc GET -t tester foo.bin")
+          (erc-send-current-line)
+          (should (eq t (plist-get (car erc-dcc-list) :turbo)))
+          (should (equal (pop calls) (list elt "foo.bin" proc))))
+
+        (ert-info ("Arg turbo in pos 4")
+          (setq elt (plist-put elt :turbo nil)
+                erc-dcc-list (list elt))
+          (goto-char erc-input-marker)
+          (insert "/dcc GET tester -t foo.bin")
+          (erc-send-current-line)
+          (should (eq t (plist-get (car erc-dcc-list) :turbo)))
+          (should (equal (pop calls) (list elt "foo.bin" proc))))
+
+        (ert-info ("Arg turbo in pos 6")
+          (setq elt (plist-put elt :turbo nil)
+                erc-dcc-list (list elt))
+          (goto-char erc-input-marker)
+          (insert "/dcc GET tester foo.bin -t")
+          (erc-send-current-line)
+          (should (eq t (plist-get (car erc-dcc-list) :turbo)))
+          (should (equal (pop calls) (list elt "foo.bin" proc))))))))
+
+;;; erc-dcc-tests.el ends here
diff --git a/test/lisp/erc/erc-join-tests.el b/test/lisp/erc/erc-join-tests.el
index 2eb20ffd8d..8210defbfb 100644
--- a/test/lisp/erc/erc-join-tests.el
+++ b/test/lisp/erc/erc-join-tests.el
@@ -1,6 +1,6 @@
 ;;; erc-join-tests.el --- Tests for erc-join.  -*- lexical-binding:t -*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-networks-tests.el 
b/test/lisp/erc/erc-networks-tests.el
index 588c15786e..3823c798da 100644
--- a/test/lisp/erc/erc-networks-tests.el
+++ b/test/lisp/erc/erc-networks-tests.el
@@ -1,6 +1,6 @@
 ;;; erc-networks-tests.el --- Tests for erc-networks.  -*- lexical-binding:t 
-*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 
 ;; This file is part of GNU Emacs.
 ;;
@@ -20,7 +20,6 @@
 ;;; Code:
 
 (require 'ert-x) ; cl-lib
-
 (require 'erc-networks)
 
 (defun erc-networks-tests--create-dead-proc (&optional buf)
@@ -33,6 +32,7 @@
     (set-process-query-on-exit-flag proc nil)
     proc))
 
+;; When we drop 27, call `get-buffer-create with INHIBIT-BUFFER-HOOKS.
 (defun erc-networks-tests--clean-bufs ()
   (let (erc-kill-channel-hook
         erc-kill-server-hook
@@ -55,24 +55,19 @@
                    (make-erc-networks--id-fixed :ts (float-time)
                                                 :symbol 'foo)))
 
-    ;; Dynamic
+    ;; Eliding
     (let* ((erc-network 'FooNet)
            (erc-server-current-nick "Joe")
            (identity (erc-networks--id-create nil)))
 
-      (should (equal identity
-                     #s(erc-networks--id-telescopic 0.0 FooNet
-                                                    [FooNet "joe"] 1)))
-
-      (should (equal (erc-networks--id-telescopic-grow-id identity)
-                     'FooNet/joe))
-      (should (equal identity
-                     #s(erc-networks--id-telescopic 0.0 FooNet/joe
-                                                    [FooNet "joe"] 2)))
-      (should-not (erc-networks--id-telescopic-grow-id identity))
-      (should (equal identity
-                     #s(erc-networks--id-telescopic 0.0 FooNet/joe
-                                                    [FooNet "joe"] 2))))
+      (should (equal identity #s(erc-networks--id-eliding 0.0 FooNet
+                                                          [FooNet "joe"] 1)))
+      (should (equal (erc-networks--id-eliding-grow-id identity) 'FooNet/joe))
+      (should (equal identity #s(erc-networks--id-eliding 0.0 FooNet/joe
+                                                          [FooNet "joe"] 2)))
+      (should-not (erc-networks--id-eliding-grow-id identity))
+      (should (equal identity #s(erc-networks--id-eliding 0.0 FooNet/joe
+                                                          [FooNet "joe"] 2))))
 
     ;; Compat
     (with-current-buffer (get-buffer-create "fake.chat")
@@ -95,32 +90,44 @@
                                                 :symbol 'foo)))
     (should (equal (erc-networks--id-create [h i])
                    (make-erc-networks--id-fixed :ts (float-time)
-                                                :symbol (quote \[h\ \i\]))))))
+                                                :symbol (quote \[h\ \i\]))))
+
+    (with-current-buffer (get-buffer-create "foo")
+      (let ((expected (make-erc-networks--id-fixed :ts (float-time)
+                                                   :symbol 'foo)))
+        (with-suppressed-warnings ((obsolete erc-rename-buffers))
+          (let (erc-rename-buffers)
+            (should (equal (erc-networks--id-create nil) expected))))
+        (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+          (let (erc-reuse-buffers)
+            (should (equal (erc-networks--id-create nil) expected))
+            (should (equal (erc-networks--id-create 'bar) expected)))))
+      (kill-buffer))))
 
-(ert-deftest erc-networks--id-telescopic-prefix-length ()
-  (should-not (erc-networks--id-telescopic-prefix-length
-               (make-erc-networks--id-telescopic)
-               (make-erc-networks--id-telescopic)))
+(ert-deftest erc-networks--id-eliding-prefix-length ()
+  (should-not (erc-networks--id-eliding-prefix-length
+               (make-erc-networks--id-eliding)
+               (make-erc-networks--id-eliding)))
 
-  (should-not (erc-networks--id-telescopic-prefix-length
-               (make-erc-networks--id-telescopic :parts [1 2])
-               (make-erc-networks--id-telescopic :parts [2 3])))
+  (should-not (erc-networks--id-eliding-prefix-length
+               (make-erc-networks--id-eliding :parts [1 2])
+               (make-erc-networks--id-eliding :parts [2 3])))
 
-  (should (= 1 (erc-networks--id-telescopic-prefix-length
-                (make-erc-networks--id-telescopic :parts [1])
-                (make-erc-networks--id-telescopic :parts [1 2]))))
+  (should (= 1 (erc-networks--id-eliding-prefix-length
+                (make-erc-networks--id-eliding :parts [1])
+                (make-erc-networks--id-eliding :parts [1 2]))))
 
-  (should (= 1 (erc-networks--id-telescopic-prefix-length
-                (make-erc-networks--id-telescopic :parts [1 2])
-                (make-erc-networks--id-telescopic :parts [1 3]))))
+  (should (= 1 (erc-networks--id-eliding-prefix-length
+                (make-erc-networks--id-eliding :parts [1 2])
+                (make-erc-networks--id-eliding :parts [1 3]))))
 
-  (should (= 2 (erc-networks--id-telescopic-prefix-length
-                (make-erc-networks--id-telescopic :parts [1 2])
-                (make-erc-networks--id-telescopic :parts [1 2]))))
+  (should (= 2 (erc-networks--id-eliding-prefix-length
+                (make-erc-networks--id-eliding :parts [1 2])
+                (make-erc-networks--id-eliding :parts [1 2]))))
 
-  (should (= 1 (erc-networks--id-telescopic-prefix-length
-                (make-erc-networks--id-telescopic :parts ["1"])
-                (make-erc-networks--id-telescopic :parts ["1"])))))
+  (should (= 1 (erc-networks--id-eliding-prefix-length
+                (make-erc-networks--id-eliding :parts ["1"])
+                (make-erc-networks--id-eliding :parts ["1"])))))
 
 (ert-deftest erc-networks--id-sort-buffers ()
   (let (oldest middle newest)
@@ -149,15 +156,15 @@
 
     (with-current-buffer chan-foonet-buffer
       (erc-mode)
-      (setq erc-networks--id (make-erc-networks--id-telescopic
-                              :parts [foonet "bob"] :len 1))
-      (setq erc--target (erc--target-from-string "#chan")))
+      (setq erc-networks--id (make-erc-networks--id-eliding
+                              :parts [foonet "bob"] :len 1)
+            erc--target (erc--target-from-string "#chan")))
 
     (with-current-buffer (get-buffer-create "#chan@barnet")
       (erc-mode)
-      (setq erc-networks--id (make-erc-networks--id-telescopic
-                              :parts [barnet "bob"] :len 1))
-      (setq erc--target (erc--target-from-string "#chan")))
+      (setq erc-networks--id (make-erc-networks--id-eliding
+                              :parts [barnet "bob"] :len 1)
+            erc--target (erc--target-from-string "#chan")))
 
     (kill-buffer "#chan@barnet")
     (should (equal (erc-networks-tests--bufnames "#chan") '("#chan")))
@@ -173,15 +180,15 @@
 
     (with-current-buffer bob-foonet
       (erc-mode)
-      (setq erc-networks--id (make-erc-networks--id-telescopic
-                              :parts [foonet "bob"] :len 1))
-      (setq erc--target (erc--target-from-string "bob")))
+      (setq erc-networks--id (make-erc-networks--id-eliding
+                              :parts [foonet "bob"] :len 1)
+            erc--target (erc--target-from-string "bob")))
 
     (with-current-buffer (get-buffer-create "bob@barnet")
       (erc-mode)
-      (setq erc-networks--id (make-erc-networks--id-telescopic
-                              :parts [barnet "bob"] :len 1))
-      (setq erc--target (erc--target-from-string "bob")))
+      (setq erc-networks--id (make-erc-networks--id-eliding
+                              :parts [barnet "bob"] :len 1)
+            erc--target (erc--target-from-string "bob")))
 
     (kill-buffer "bob@barnet")
     (should (equal (erc-networks-tests--bufnames "bob") '("bob")))
@@ -221,90 +228,109 @@
                    '("bob@barnet" "bob@foonet")))
     (erc-networks-tests--clean-bufs)))
 
+;; As of May 2022, this "shrink" stuff runs whenever an ERC buffer is
+;; killed because `erc-networks-shrink-ids-and-buffer-names' is a
+;; default member of all three erc-kill-* functions.
+
+;; Note: this overlaps a fair bit with the "hook" variants, i.e.,
+;; `erc-networks--shrink-ids-and-buffer-names--hook-outstanding-*' If
+;; this ever fails, just delete this and fix those.  But please copy
+;; over and adapt the comments first.
+
 (ert-deftest erc-networks--shrink-ids-and-buffer-names--perform-outstanding ()
-  ;; Not collapsed because we have one collision outstanding.
+  ;; While some buffer #a@barnet/dummy is being killed, its display ID
+  ;; is not collapsed because collisions still exist.
   ;;
-  ;; Overlaps with quite a bit with the
-  ;; `erc-networks--shrink-ids-and-buffer-names--hook-outstanding-*' stuff
-  ;; below.  If this ever fails, just delete this and fix those.
-
-  ;; Presumably, some buffer foonet/chester was just killed
+  ;; Note that we don't have to set `erc-server-connected' because
+  ;; this function is intentionally connectivity agnostic.
   (with-current-buffer (get-buffer-create "foonet/tester")
     (erc-mode)
-    (setq erc-network 'foonet
-          erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+    (setq erc-server-current-nick "tester" ; Always set (`erc-open')
+          ;; Set when transport connected
+          erc-server-process (erc-networks-tests--create-live-proc)
+          ;; Both set just before IRC (logically) connected (post MOTD)
+          erc-network 'foonet
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'foonet/tester
                             :parts [foonet "tester"]
-                            :len 2)
-          erc-server-process (erc-networks-tests--create-live-proc)))
+                            :len 2))) ; is/was a plain foonet collision
+
+  ;; Presumably, some server buffer named foonet/dummy was just
+  ;; killed, hence the length 2 display ID.
 
+  ;; A target buffer for chan #a exists for foonet/tester.  The
+  ;; precise form of its name should not affect shrinking.
   (with-current-buffer (get-buffer-create
                         (elt ["#a" "#a@foonet" "#a@foonet/tester"] (random 3)))
     (erc-mode)
-    (setq erc-server-process (with-current-buffer "foonet/tester"
-                               erc-server-process)
+    (setq erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "foonet/tester"))
           erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-networks--id (with-current-buffer "foonet/tester"
-                             erc-networks--id)
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "foonet/tester"))
           erc--target (erc--target-from-string "#a")))
 
+  ;; Another network context exists (so we have buffers to iterate
+  ;; over), and it's also part of a collision group.
   (with-current-buffer (get-buffer-create "barnet/tester")
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'barnet/tester
                             :parts [barnet "tester"]
                             :len 2)
           erc-server-process (erc-networks-tests--create-live-proc)))
 
-  (with-current-buffer (get-buffer-create "barnet/chester")
+  (with-current-buffer (get-buffer-create "barnet/dummy")
     (erc-mode)
     (setq erc-network 'barnet
-          erc-server-current-nick "chester"
-          erc-networks--id (make-erc-networks--id-telescopic
-                            :symbol 'barnet/chester
-                            :parts [barnet "chester"]
+          erc-server-current-nick "dummy"
+          erc-networks--id (make-erc-networks--id-eliding
+                            :symbol 'barnet/dummy
+                            :parts [barnet "dummy"]
                             :len 2)
           erc-server-process (erc-networks-tests--create-live-proc)))
 
-  ;; Presumably, some buffer #a@barnet/chester was just killed
+  ;; The buffer being killed is not part of the foonet collision
+  ;; group, which contains one display ID eligible for shrinkage.
   (with-current-buffer (get-buffer-create
                         (elt ["#a@barnet" "#a@barnet/tester"] (random 2)))
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-server-process (with-current-buffer "barnet/tester"
-                               erc-server-process)
-          erc-networks--id (with-current-buffer "barnet/tester"
-                             erc-networks--id)
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "barnet/tester"))
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "barnet/tester"))
           erc--target (erc--target-from-string "#a")))
 
-  (with-temp-buffer
-    (setq erc-networks--id (make-erc-networks--id-telescopic))
+  (with-temp-buffer ; doesn't matter what the current buffer is
+    (setq erc-networks--id (make-erc-networks--id-eliding)) ; mock
     (erc-networks--shrink-ids-and-buffer-names))
 
   (should (equal (mapcar #'buffer-name (erc-buffer-list))
-                 '("foonet"
-                   "#a@foonet"
+                 '("foonet" ; shrunk
+                   "#a@foonet" ; shrunk
                    "barnet/tester"
-                   "barnet/chester"
+                   "barnet/dummy"
                    "#a@barnet/tester")))
 
   (erc-networks-tests--clean-bufs))
 
-(ert-deftest erc-networks--shrink-ids-and-buffer-names--perform-collapse ()
-  ;; Overlaps with `erc-networks--shrink-ids-and-buffer-names--collapse-hook-*'
-  ;; quite a bit.  If this ever fails, just delete it and fix ^.
+;; This likewise overlaps with the "hook" variants below.  If this
+;; should ever fail, just delete it and optionally fix those.
 
-  ;; Presumably, some buffer foonet/chester was just killed
+(ert-deftest erc-networks--shrink-ids-and-buffer-names--perform-collapse ()
+  ;; This is similar to the "outstanding" variant above, but both
+  ;; groups are eligible for renaming, which is abnormal but possible
+  ;; when recovering from some mishap.
   (with-current-buffer (get-buffer-create "foonet/tester")
     (erc-mode)
     (setq erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'foonet/tester
                             :parts [foonet "tester"]
                             :len 2)
@@ -313,19 +339,19 @@
   (with-current-buffer
       (get-buffer-create (elt ["#a" "#a@foonet/tester"] (random 2)))
     (erc-mode)
-    (setq erc-server-process (with-current-buffer "foonet/tester"
-                               erc-server-process)
+    (setq erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "foonet/tester"))
           erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-networks--id (with-current-buffer "foonet/tester"
-                             erc-networks--id)
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "foonet/tester"))
           erc--target (erc--target-from-string "#a")))
 
   (with-current-buffer (get-buffer-create "barnet/tester")
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'barnet/tester
                             :parts [barnet "tester"]
                             :len 2)
@@ -336,14 +362,14 @@
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-server-process (with-current-buffer "barnet/tester"
-                               erc-server-process)
-          erc-networks--id (with-current-buffer "barnet/tester"
-                             erc-networks--id)
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "barnet/tester"))
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "barnet/tester"))
           erc--target (erc--target-from-string "#b")))
 
   (with-temp-buffer
-    (setq erc-networks--id (make-erc-networks--id-telescopic))
+    (setq erc-networks--id (make-erc-networks--id-eliding))
     (erc-networks--shrink-ids-and-buffer-names))
 
   (should (equal (mapcar #'buffer-name (erc-buffer-list))
@@ -357,7 +383,7 @@
     (erc-mode)
     (setq erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'foonet/tester
                             :parts [foonet "tester"]
                             :len 2)
@@ -365,31 +391,31 @@
 
   (with-current-buffer (get-buffer-create "#a@foonet/tester")
     (erc-mode)
-    (setq erc-server-process (with-current-buffer "foonet/tester"
-                               erc-server-process)
+    (setq erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "foonet/tester"))
           erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-networks--id (with-current-buffer "foonet/tester"
-                             erc-networks--id)
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "foonet/tester"))
           erc--target (erc--target-from-string "#a")))
 
   (with-current-buffer (get-buffer-create "barnet/tester")
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'barnet/tester
                             :parts [barnet "tester"]
                             :len 2)
           erc-server-process (erc-networks-tests--create-live-proc)))
 
-  (with-current-buffer (get-buffer-create "barnet/chester")
+  (with-current-buffer (get-buffer-create "barnet/dummy")
     (erc-mode)
     (setq erc-network 'barnet
-          erc-server-current-nick "chester"
-          erc-networks--id (make-erc-networks--id-telescopic
-                            :symbol 'barnet/chester
-                            :parts [barnet "chester"]
+          erc-server-current-nick "dummy"
+          erc-networks--id (make-erc-networks--id-eliding
+                            :symbol 'barnet/dummy
+                            :parts [barnet "dummy"]
                             :len 2)
           erc-server-process (erc-networks-tests--create-live-proc)))
 
@@ -397,56 +423,55 @@
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-server-process (with-current-buffer "barnet/tester"
-                               erc-server-process)
-          erc-networks--id (with-current-buffer "barnet/tester"
-                             erc-networks--id)
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "barnet/tester"))
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "barnet/tester"))
           erc--target (erc--target-from-string "#a"))))
 
 (ert-deftest erc-networks--shrink-ids-and-buffer-names--hook-outstanding-srv ()
   (erc-networks--shrink-ids-and-buffer-names--hook-outstanding-common)
-  (with-current-buffer (get-buffer-create "foonet/chester")
+  (with-current-buffer (get-buffer-create "foonet/dummy")
     (erc-mode)
     (setq erc-network 'foonet
-          erc-server-current-nick "chester"
-          erc-networks--id (make-erc-networks--id-telescopic
-                            :symbol 'foonet/chester
-                            :parts [foonet "chester"]
+          erc-server-current-nick "dummy"
+          erc-networks--id (make-erc-networks--id-eliding
+                            :symbol 'foonet/dummy
+                            :parts [foonet "dummy"]
                             :len 2)
-          erc-server-process (erc-networks-tests--create-live-proc)))
-
-  (with-current-buffer "foonet/chester" (kill-buffer))
+          erc-server-process (erc-networks-tests--create-live-proc))
+    (kill-buffer))
 
   (should (equal (mapcar #'buffer-name (erc-buffer-list))
                  '("foonet"
                    "#a@foonet"
                    "barnet/tester"
-                   "barnet/chester"
+                   "barnet/dummy"
                    "#a@barnet/tester")))
   (erc-networks-tests--clean-bufs))
 
 (ert-deftest erc-networks--shrink-ids-and-buffer-names--hook-outstanding-tgt ()
   (erc-networks--shrink-ids-and-buffer-names--hook-outstanding-common)
-  (with-current-buffer (get-buffer-create "#a@foonet/chester")
+  (with-current-buffer (get-buffer-create "#a@foonet/dummy")
     (erc-mode)
     (setq erc-network 'foonet
-          erc-server-current-nick "chester"
-          erc-networks--id (make-erc-networks--id-telescopic
-                            :symbol 'foonet/chester
-                            :parts [foonet "chester"]
+          erc-server-current-nick "dummy"
+          erc-networks--id (make-erc-networks--id-eliding
+                            :symbol 'foonet/dummy
+                            :parts [foonet "dummy"]
                             :len 2)
           erc--target (erc--target-from-string "#a")
           erc-server-process (with-temp-buffer
                                (erc-networks-tests--create-dead-proc))))
 
-  (with-current-buffer "#a@foonet/chester" (kill-buffer))
+  (with-current-buffer "#a@foonet/dummy" (kill-buffer))
 
   ;; Identical to *-server variant above
   (should (equal (mapcar #'buffer-name (erc-buffer-list))
                  '("foonet"
                    "#a@foonet"
                    "barnet/tester"
-                   "barnet/chester"
+                   "barnet/dummy"
                    "#a@barnet/tester")))
   (erc-networks-tests--clean-bufs))
 
@@ -461,7 +486,7 @@
   (should (equal (mapcar #'buffer-name (erc-buffer-list))
                  '("foonet"
                    "barnet/tester"
-                   "barnet/chester"
+                   "barnet/dummy"
                    "#a")))
 
   (erc-networks-tests--clean-bufs))
@@ -472,7 +497,7 @@
     (erc-mode)
     (setq erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'foonet/tester
                             :parts [foonet "tester"]
                             :len 2)
@@ -480,19 +505,19 @@
 
   (with-current-buffer (get-buffer-create "#a@foonet/tester")
     (erc-mode)
-    (setq erc-server-process (with-current-buffer "foonet/tester"
-                               erc-server-process)
+    (setq erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "foonet/tester"))
           erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-networks--id (with-current-buffer "foonet/tester"
-                             erc-networks--id)
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "foonet/tester"))
           erc--target (erc--target-from-string "#a")))
 
   (with-current-buffer (get-buffer-create "barnet/tester")
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'barnet/tester
                             :parts [barnet "tester"]
                             :len 2)
@@ -502,10 +527,10 @@
     (erc-mode)
     (setq erc-network 'barnet
           erc-server-current-nick "tester"
-          erc-server-process (with-current-buffer "barnet/tester"
-                               erc-server-process)
-          erc-networks--id (with-current-buffer "barnet/tester"
-                             erc-networks--id)
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "barnet/tester"))
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "barnet/tester"))
           erc--target (erc--target-from-string "#b")))
 
   (funcall check)
@@ -518,29 +543,27 @@
 (ert-deftest erc-networks--shrink-ids-and-buffer-names--hook-collapse-server ()
   (erc-networks--shrink-ids-and-buffer-names--hook-collapse
    (lambda ()
-     (with-current-buffer (get-buffer-create "foonet/chester")
+     (with-current-buffer (get-buffer-create "foonet/dummy")
        (erc-mode)
        (setq erc-network 'foonet
-             erc-server-current-nick "chester"
-             erc-networks--id (make-erc-networks--id-telescopic
-                               :symbol 'foonet/chester
-                               :parts [foonet "chester"]
+             erc-server-current-nick "dummy"
+             erc-networks--id (make-erc-networks--id-eliding
+                               :symbol 'foonet/dummy
+                               :parts [foonet "dummy"]
                                :len 2)
-             erc-server-process (erc-networks-tests--create-live-proc)))
-
-     (with-current-buffer "foonet/chester"
+             erc-server-process (erc-networks-tests--create-live-proc))
        (kill-buffer)))))
 
 (ert-deftest erc-networks--shrink-ids-and-buffer-names--hook-collapse-target ()
   (erc-networks--shrink-ids-and-buffer-names--hook-collapse
    (lambda ()
-     (with-current-buffer (get-buffer-create "#a@foonet/chester")
+     (with-current-buffer (get-buffer-create "#a@foonet/dummy")
        (erc-mode)
        (setq erc-network 'foonet
-             erc-server-current-nick "chester"
-             erc-networks--id (make-erc-networks--id-telescopic
-                               :symbol 'foonet/chester
-                               :parts [foonet "chester"]
+             erc-server-current-nick "dummy"
+             erc-networks--id (make-erc-networks--id-eliding
+                               :symbol 'foonet/dummy
+                               :parts [foonet "dummy"]
                                :len 2)
              ;; `erc-kill-buffer-function' uses legacy target detection
              ;; but falls back on buffer name, so no need for:
@@ -549,9 +572,8 @@
              ;;
              erc--target (erc--target-from-string "#a")
              erc-server-process (with-temp-buffer
-                                  (erc-networks-tests--create-dead-proc))))
-
-     (with-current-buffer "#a@foonet/chester" (kill-buffer)))))
+                                  (erc-networks-tests--create-dead-proc)))
+       (kill-buffer)))))
 
 ;; FIXME this test is old and may describe impossible states:
 ;; leftover identities being qual-equal but not eq (implies
@@ -571,7 +593,8 @@
 
   (with-current-buffer (get-buffer-create "#chan") ; prior session
     (erc-mode)
-    (setq erc-server-process (with-current-buffer "foonet" erc-server-process)
+    (setq erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "foonet"))
           erc--target (erc--target-from-string "#chan")
           erc-networks--id (erc-networks--id-create nil)))
 
@@ -587,7 +610,8 @@
     (setq erc--target (erc--target-from-string "#chan")
           erc-network 'foonet
           erc-server-current-nick "tester"
-          erc-server-process (with-current-buffer "foonet" erc-server-process)
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "foonet"))
           erc-networks--id (erc-networks--id-create nil)))
 
   (with-current-buffer (get-buffer-create "#chan@foonet<dead>")
@@ -641,13 +665,15 @@
   (with-current-buffer (get-buffer-create "#chan") ; prior session
     (erc-mode)
     (setq erc-networks--id (erc-networks--id-create 'oofnet)
-          erc-server-process (with-current-buffer "oofnet" erc-server-process)
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "oofnet"))
           erc--target (erc--target-from-string "#chan")))
 
   (with-current-buffer (get-buffer-create "#chan@oofnet") ;dupe/not collision
     (erc-mode)
     (setq erc-networks--id (erc-networks--id-create 'oofnet)
-          erc-server-process (with-current-buffer "oofnet" erc-server-process)
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "oofnet"))
           erc--target (erc--target-from-string "#chan")))
 
   (with-current-buffer "oofnet"
@@ -735,8 +761,8 @@
        (with-current-buffer (get-buffer-create "#chan@barnet")
          (erc-mode)
          (setq erc--target (erc--target-from-string "#chan")
-               erc-server-process (with-current-buffer "barnet"
-                                    erc-server-process)
+               erc-server-process (buffer-local-value 'erc-server-process
+                                                      (get-buffer "barnet"))
                erc-networks--id (erc-networks--id-create nil)))
        (with-current-buffer (get-buffer-create "foonet")
          (erc-mode)
@@ -754,7 +780,8 @@
 
 (defun erc-tests--prep-erc-networks--reconcile-buffer-names--no-srv-buf-given
     (check)
-  (let ((oofnet-proc (with-temp-buffer 
(erc-networks-tests--create-dead-proc))))
+  (let ((oofnet-proc (with-temp-buffer
+                       (erc-networks-tests--create-dead-proc))))
 
     (with-current-buffer (get-buffer-create "rabnet")
       (erc-mode)
@@ -799,10 +826,10 @@
        (with-current-buffer (get-buffer-create "#chan@rabnet")
          (erc-mode)
          (setq erc--target (erc--target-from-string "#chan")
-               erc-server-process (with-current-buffer "rabnet"
-                                    erc-server-process)
-               erc-networks--id (with-current-buffer "rabnet"
-                                  erc-networks--id)))
+               erc-server-process (buffer-local-value 'erc-server-process
+                                                      (get-buffer "rabnet"))
+               erc-networks--id (buffer-local-value 'erc-networks--id
+                                                    (get-buffer "rabnet"))))
 
        (with-current-buffer (get-buffer-create "oofnet")
          (erc-mode)
@@ -862,8 +889,8 @@
           erc-server-process (erc-networks-tests--create-dead-proc)
           erc-networks--id (erc-networks--id-create nil))) ; derived
 
-  (with-current-buffer (get-buffer-create (elt ["#chan" "#chan@foonet"]
-                                               (random 2)))
+  (with-current-buffer
+      (get-buffer-create (elt ["#chan" "#chan@foonet"] (random 2)))
     (erc-mode)
     (setq erc--target (erc--target-from-string "#chan"))
     (cl-multiple-value-setq (erc-server-process erc-networks--id)
@@ -1030,7 +1057,7 @@
     (erc-mode)
     (setq erc-network 'foonet
           erc-server-current-nick "bob"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'foonet/bob
                             :parts [foonet "bob"]
                             :len 2)
@@ -1040,10 +1067,10 @@
                         (elt ["#chan@foonet" "#chan@foonet/bob"] (random 2)))
     (erc-mode)
     (setq erc--target (erc--target-from-string "#chan")
-          erc-server-process (with-current-buffer "foonet/bob"
-                               erc-server-process)
-          erc-networks--id (with-current-buffer "foonet/bob"
-                             erc-networks--id)))
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "foonet/bob"))
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "foonet/bob"))))
 
   (with-current-buffer (get-buffer-create "barnet")
     (erc-mode)
@@ -1055,16 +1082,16 @@
   (with-current-buffer (get-buffer-create "#chan@barnet")
     (erc-mode)
     (setq erc--target (erc--target-from-string "#chan")
-          erc-server-process (with-current-buffer "barnet"
-                               erc-server-process)
-          erc-networks--id (with-current-buffer "barnet"
-                             erc-networks--id)))
+          erc-server-process (buffer-local-value 'erc-server-process
+                                                 (get-buffer "barnet"))
+          erc-networks--id (buffer-local-value 'erc-networks--id
+                                               (get-buffer "barnet"))))
 
   (with-current-buffer (get-buffer-create "foonet/alice")
     (erc-mode)
     (setq erc-network 'foonet
           erc-server-current-nick "alice"
-          erc-networks--id (make-erc-networks--id-telescopic
+          erc-networks--id (make-erc-networks--id-eliding
                             :symbol 'foonet/alice
                             :parts [foonet "alice"]
                             :len 2)
@@ -1136,6 +1163,30 @@
 
     (erc-networks-tests--clean-bufs)))
 
+(ert-deftest erc-networks--ensure-announced ()
+  (with-current-buffer (get-buffer-create "localhost:6667")
+    (should (local-variable-if-set-p 'erc-server-announced-name))
+    (let (erc-insert-modify-hook
+          (erc-server-process (erc-networks-tests--create-live-proc))
+          (parsed (make-erc-response
+                   :unparsed ":irc.barnet.org 422 tester :MOTD File is missing"
+                   :sender "irc.barnet.org"
+                   :command "422"
+                   :command-args '("tester" "MOTD File is missing")
+                   :contents "MOTD File is missing")))
+
+      (erc-mode) ; boilerplate displayable start (needs `erc-server-process')
+      (insert "\n\n")
+      (setq erc-input-marker (make-marker) erc-insert-marker (make-marker))
+      (set-marker erc-insert-marker (point-max))
+      (erc-display-prompt) ; boilerplate displayable end
+
+      (erc-networks--ensure-announced erc-server-process parsed)
+      (goto-char (point-min))
+      (search-forward "Failed")
+      (should (string= erc-server-announced-name "irc.barnet.org")))
+    (when noninteractive (kill-buffer))))
+
 (ert-deftest erc-networks--rename-server-buffer--no-existing--orphan ()
   (with-current-buffer (get-buffer-create "#chan")
     (erc-mode)
@@ -1207,49 +1258,60 @@
 
   (erc-networks-tests--clean-bufs))
 
-(ert-deftest erc-networks--rename-server-buffer--existing--noreuse ()
-  (should erc-reuse-buffers) ; default
-  (let* ((old-buf (get-buffer-create "FooNet"))
-         (old-proc (erc-networks-tests--create-dead-proc old-buf))
-         erc-reuse-buffers)
-    (with-current-buffer old-buf
-      (erc-mode)
-      (insert "*** Old buf")
-      (setq erc-network 'FooNet
-            erc-server-current-nick "tester"
-            erc-insert-marker (set-marker (make-marker) (point-max))
-            erc-server-process old-proc
-            erc-networks--id (erc-networks--id-create nil)))
-    (with-current-buffer (get-buffer-create "#chan")
-      (erc-mode)
-      (setq erc-network 'FooNet
-            erc-server-process old-proc
-            erc-networks--id (erc-networks--id-create nil)
-            erc--target (erc--target-from-string "#chan")))
+;; This is for compatibility with pre-28.1 behavior.  Basically, we're
+;; trying to match the behavior bug for bug.  All buffers were always
+;; suffixed and never reassociated.  28.1 introduced a regression that
+;; reversed the latter, but we've reverted that.
 
-    (ert-info ("Server buffer uniquely renamed")
-      (with-current-buffer (get-buffer-create "irc.foonet.org")
+(ert-deftest erc-networks--rename-server-buffer--existing--noreuse ()
+  (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+    (should erc-reuse-buffers) ; default
+    (let* ((old-buf (get-buffer-create "irc.foonet.org:6697/irc.foonet.org"))
+           (old-proc (erc-networks-tests--create-dead-proc old-buf))
+           erc-reuse-buffers)
+      (with-current-buffer old-buf
         (erc-mode)
+        (insert "*** Old buf")
         (setq erc-network 'FooNet
               erc-server-current-nick "tester"
-              erc-server-process (erc-networks-tests--create-live-proc)
-              erc-networks--id (erc-networks--id-create nil))
-        (should-not (erc-networks--rename-server-buffer erc-server-process))
-        (should (string= (buffer-name) "FooNet<2>"))
-        (goto-char (point-min))
-        (should-not (search-forward "Old buf" nil t))))
-
-    (ert-info ("Channel buffer reassociated")
-      (erc-server-process-alive "#chan")
-      (with-current-buffer "#chan"
-        (should erc-server-connected)
-        (should-not (eq erc-server-process old-proc))
-        (erc-with-server-buffer
-          (should (string= (buffer-name) "FooNet<2>")))))
-
-    (ert-info ("Old buffer still around")
-      (should (buffer-live-p old-buf))))
-
+              erc-insert-marker (set-marker (make-marker) (point-max))
+              erc-server-process old-proc
+              erc-networks--id (erc-networks--id-create nil)))
+      (with-current-buffer (get-buffer-create "#chan")
+        (erc-mode)
+        (setq erc-network 'FooNet
+              erc-server-process old-proc
+              erc-networks--id (buffer-local-value 'erc-networks--id old-buf)
+              erc--target (erc--target-from-string "#chan"))
+        (rename-buffer (erc-networks--construct-target-buffer-name 
erc--target)))
+
+      (ert-info ("Server buffer uniquely renamed")
+        (with-current-buffer
+            (get-buffer-create "irc.foonet.org:6697/irc.foonet.org<2>")
+          (erc-mode)
+          (setq erc-network 'FooNet
+                erc-server-current-nick "tester"
+                erc-server-process (erc-networks-tests--create-live-proc)
+                erc-networks--id (erc-networks--id-create nil))
+          (should-not (erc-networks--rename-server-buffer erc-server-process))
+          (should (string= (buffer-name)
+                           "irc.foonet.org:6697/irc.foonet.org<2>"))
+          (goto-char (point-min))
+          (should-not (search-forward "Old buf" nil t))))
+
+      (ert-info ("Channel buffer not reassociated")
+        (should-not
+         (erc-server-process-alive
+          (should (get-buffer "#chan/irc.foonet.org"))))
+        (with-current-buffer (get-buffer "#chan/irc.foonet.org")
+          (should-not erc-server-connected)
+          (should (eq erc-server-process old-proc))
+          (erc-with-server-buffer
+            (should (string= (buffer-name)
+                             "irc.foonet.org:6697/irc.foonet.org")))))
+
+      (ert-info ("Old buffer still around")
+        (should (buffer-live-p old-buf)))))
   (erc-networks-tests--clean-bufs))
 
 (ert-deftest erc-networks--rename-server-buffer--reconnecting ()
@@ -1490,50 +1552,49 @@
 (ert-deftest erc-networks--update-server-identity--double-existing ()
   (with-temp-buffer
     (erc-mode)
-    (setq erc-networks--id (make-erc-networks--id-telescopic
-                            :parts [foonet "bob"]
-                            :len 1))
+    (setq erc-networks--id (make-erc-networks--id-eliding
+                            :parts [foonet "bob"] :len 1))
 
     (with-current-buffer (get-buffer-create "#chan@foonet/bob")
       (erc-mode)
-      (setq erc-networks--id (make-erc-networks--id-telescopic
-                              :parts [foonet "bob"]
-                              :len 2)))
+      (setq erc-networks--id (make-erc-networks--id-eliding
+                              :parts [foonet "bob"] :len 2)))
     (with-current-buffer (get-buffer-create "foonet/alice")
       (erc-mode)
       (setq erc-networks--id
-            (make-erc-networks--id-telescopic :parts [foonet "alice"] :len 2)))
+            (make-erc-networks--id-eliding :parts [foonet "alice"] :len 2)))
 
     (ert-info ("Adopt equivalent identity")
       (should (eq (erc-networks--update-server-identity)
-                  (with-current-buffer "#chan@foonet/bob" erc-networks--id))))
+                  (buffer-local-value 'erc-networks--id
+                                      (get-buffer "#chan@foonet/bob")))))
 
     (ert-info ("Ignore non-matches")
       (should-not (erc-networks--update-server-identity))
       (should (eq erc-networks--id
-                  (with-current-buffer "#chan@foonet/bob" erc-networks--id)))))
+                  (buffer-local-value 'erc-networks--id
+                                      (get-buffer "#chan@foonet/bob"))))))
 
   (erc-networks-tests--clean-bufs))
 
 (ert-deftest erc-networks--update-server-identity--double-new ()
   (with-temp-buffer
     (erc-mode)
-    (setq erc-networks--id (make-erc-networks--id-telescopic
-                            :parts [foonet "bob"]
-                            :len 1))
+    (setq erc-networks--id (make-erc-networks--id-eliding
+                            :parts [foonet "bob"] :len 1))
 
     (with-current-buffer (get-buffer-create "foonet/alice")
       (erc-mode)
       (setq erc-networks--id
-            (make-erc-networks--id-telescopic :parts [foonet "alice"] :len 2)))
+            (make-erc-networks--id-eliding :parts [foonet "alice"] :len 2)))
     (with-current-buffer (get-buffer-create "#chan@foonet/alice")
       (erc-mode)
-      (setq erc-networks--id (with-current-buffer "foonet/alice"
-                               erc-networks--id)))
+      (setq erc-networks--id (buffer-local-value 'erc-networks--id
+                                                 (get-buffer "foonet/alice"))))
 
     (ert-info ("Evolve identity to prevent ambiguity")
       (should-not (erc-networks--update-server-identity))
-      (should (= (erc-networks--id-telescopic-len erc-networks--id) 2))
+      (should (= (erc-networks--id-eliding-len erc-networks--id) 2))
       (should (eq (erc-networks--id-symbol erc-networks--id) 'foonet/bob))))
 
   (erc-networks-tests--clean-bufs))
@@ -1541,22 +1602,22 @@
 (ert-deftest erc-networks--update-server-identity--double-bounded ()
   (with-temp-buffer
     (erc-mode)
-    (setq erc-networks--id (make-erc-networks--id-telescopic
-                            :parts [foonet "bob"]
-                            :len 1))
+    (setq erc-networks--id (make-erc-networks--id-eliding
+                            :parts [foonet "bob"] :len 1))
 
     (with-current-buffer (get-buffer-create "foonet/alice/home")
       (erc-mode)
-      (setq erc-networks--id (make-erc-networks--id-telescopic
+      (setq erc-networks--id (make-erc-networks--id-eliding
                               :parts [foonet "alice" home] :len 3)))
     (with-current-buffer (get-buffer-create "#chan@foonet/alice/home")
       (erc-mode)
-      (setq erc-networks--id (with-current-buffer "foonet/alice/home"
-                               erc-networks--id)))
+      (setq erc-networks--id
+            (buffer-local-value 'erc-networks--id
+                                (get-buffer "foonet/alice/home"))))
 
     (ert-info ("Evolve identity to prevent ambiguity")
       (should-not (erc-networks--update-server-identity))
-      (should (= (erc-networks--id-telescopic-len erc-networks--id) 2))
+      (should (= (erc-networks--id-eliding-len erc-networks--id) 2))
       (should (eq (erc-networks--id-symbol erc-networks--id) 'foonet/bob))))
 
   (erc-networks-tests--clean-bufs))
@@ -1565,20 +1626,21 @@
   (with-temp-buffer
     (erc-mode)
     (setq erc-networks--id
-          (make-erc-networks--id-telescopic :parts [foonet "bob"] :len 1))
+          (make-erc-networks--id-eliding :parts [foonet "bob"] :len 1))
 
     (with-current-buffer (get-buffer-create "foonet")
       (erc-mode)
       (setq erc-networks--id
-            (make-erc-networks--id-telescopic :parts [foonet "alice"] :len 1)))
+            (make-erc-networks--id-eliding :parts [foonet "alice"] :len 1)))
     (with-current-buffer (get-buffer-create "#chan")
       (erc-mode)
       (setq erc--target (erc--target-from-string "#chan")
-            erc-networks--id (with-current-buffer "foonet" erc-networks--id)))
+            erc-networks--id (buffer-local-value 'erc-networks--id
+                                                 (get-buffer "foonet"))))
 
     (ert-info ("Evolve identity to prevent ambiguity")
       (should-not (erc-networks--update-server-identity))
-      (should (= (erc-networks--id-telescopic-len erc-networks--id) 2))
+      (should (= (erc-networks--id-eliding-len erc-networks--id) 2))
       (should (eq (erc-networks--id-symbol erc-networks--id) 'foonet/bob)))
 
     (ert-info ("Collision renamed")
@@ -1595,21 +1657,22 @@
   (with-temp-buffer
     (erc-mode)
     (setq erc-networks--id
-          (make-erc-networks--id-telescopic :parts [foonet "bob" home] :len 1))
+          (make-erc-networks--id-eliding :parts [foonet "bob" home] :len 1))
 
     (with-current-buffer (get-buffer-create "foonet/bob/office")
       (erc-mode)
       (setq erc-networks--id
-            (make-erc-networks--id-telescopic :parts [foonet "bob" office]
-                                              :len 3)))
+            (make-erc-networks--id-eliding :parts [foonet "bob" office]
+                                           :len 3)))
     (with-current-buffer (get-buffer-create "#chan@foonet/bob/office")
       (erc-mode)
-      (setq erc-networks--id (with-current-buffer "foonet/bob/office"
-                               erc-networks--id)))
+      (setq erc-networks--id
+            (buffer-local-value 'erc-networks--id
+                                (get-buffer "foonet/bob/office"))))
 
     (ert-info ("Extend our identity's canonical ID so that it's unique")
       (should-not (erc-networks--update-server-identity))
-      (should (= (erc-networks--id-telescopic-len erc-networks--id) 3))))
+      (should (= (erc-networks--id-eliding-len erc-networks--id) 3))))
 
   (erc-networks-tests--clean-bufs))
 
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-auth-source.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-auth-source.el
index bb8d7ecad3..3d399a1815 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-auth-source.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-auth-source.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-auth-source.el --- auth-source scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -76,8 +76,8 @@
 
 (ert-deftest erc-scenarios-base-auth-source-server--netid-custom ()
   :tags '(:expensive-test)
-  (let ((erc-auth-source-parameters-server-function
-         (lambda (&rest _) (list :host "MyHost"))))
+  (let ((erc-auth-source-server-function
+         (lambda (&rest _) (erc-auth-source-search :host "MyHost"))))
     (erc-scenarios-common--auth-source
      'MySession 'foonet
      "machine 127.0.0.1 port %d user tester password fake"
@@ -86,12 +86,12 @@
 
 (ert-deftest erc-scenarios-base-auth-source-server--nopass ()
   :tags '(:expensive-test)
-  (let (erc-auth-source-parameters-server-function)
+  (let (erc-auth-source-server-function)
     (erc-scenarios-common--auth-source nil 'nopass)))
 
 (ert-deftest erc-scenarios-base-auth-source-server--nopass-netid ()
   :tags '(:expensive-test)
-  (let (erc-auth-source-parameters-server-function)
+  (let (erc-auth-source-server-function)
     (erc-scenarios-common--auth-source 'MySession 'nopass)))
 
 ;; Identify via auth source with no initial password
@@ -137,7 +137,7 @@
 (ert-deftest erc-scenarios-services-auth-source--network ()
   :tags '(:expensive-test)
   ;; Skip consulting auth-source for the server password (PASS).
-  (let (erc-auth-source-parameters-server-function)
+  (let (erc-auth-source-server-function)
     (erc-scenarios-common--services-auth-source
      "machine 127.0.0.1 port %d user tester password spam"
      "machine zirconium.libera.chat port %d user tester password fake"
@@ -152,7 +152,7 @@
 
 (ert-deftest erc-scenarios-services-auth-source--announced ()
   :tags '(:expensive-test)
-  (let (erc-auth-source-parameters-server-function)
+  (let (erc-auth-source-server-function)
     (erc-scenarios-common--services-auth-source
      "machine 127.0.0.1 port %d user tester password spam"
      "machine zirconium.libera.chat port %d user tester password changeme")))
@@ -161,15 +161,15 @@
   :tags '(:expensive-test)
   ;; Support legacy host -> domain name
   ;; (likely most common in real configs)
-  (let (erc-auth-source-parameters-server-function)
+  (let (erc-auth-source-server-function)
     (erc-scenarios-common--services-auth-source
      "machine 127.0.0.1 port %d user tester password changeme")))
 
 (ert-deftest erc-scenarios-services-auth-source--custom ()
   :tags '(:expensive-test)
-  (let (erc-auth-source-parameters-server-function
-        (erc-auth-source-parameters-services-function
-         (lambda (&rest _) '(:host "MyAccount"))))
+  (let (erc-auth-source-server-function
+        (erc-auth-source-services-function
+         (lambda (&rest _) (erc-auth-source-search :host "MyAccount"))))
     (erc-scenarios-common--services-auth-source
      "machine zirconium.libera.chat port %d user tester password spam"
      "machine MyAccount port %d user tester password changeme"
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-nick.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-nick.el
index 3152542ecf..75b0e8d654 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-nick.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-nick.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-association-nick.el --- base assoc scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-samenet.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-samenet.el
index d27495b584..f7b0553ee3 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-samenet.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-samenet.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-association-samenet.el --- assoc samenet scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association.el
index ed94dfab50..47fc4eed17 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-association.el --- base assoc scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-compat-rename-bouncer.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-compat-rename-bouncer.el
index a513c1c933..9adc402e0a 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-compat-rename-bouncer.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-compat-rename-bouncer.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-compat-rename-bouncer.el --- compat-rename scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-misc-regressions.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-misc-regressions.el
index 1ec18259f0..70214a92f7 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-misc-regressions.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-misc-regressions.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-misc-regressions.el --- misc regressions scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -25,11 +25,6 @@
 
 (eval-when-compile (require 'erc-join))
 
-;; The added complexity of a request handler definitely stinks. But on
-;; some machines, the ordering from the selector is nondeterministic,
-;; whereas normally, the filter for the last process created (in the
-;; code) gets all the initial attention. FIXME delete obsolete comment
-
 (defun erc-scenarios--rebuffed-gapless-pass-handler (dialog exchange)
   (when (eq (erc-d-dialog-name dialog) 'pass-stub)
     (let* ((match (erc-d-exchange-match exchange 1))
@@ -38,10 +33,10 @@
       (erc-d-load-replacement-dialog dialog sym 1))))
 
 (ert-deftest erc-scenarios-base-gapless-connect ()
-  :tags '(:expensive-test)
   "Back-to-back entry-point invocations happen successfully.
 Originally from scenario rebuffed/gapless as explained in Bug#48598:
 28.0.50; buffer-naming collisions involving bouncers in ERC."
+  :tags '(:expensive-test)
   (erc-scenarios-common-with-cleanup
       ((erc-scenarios-common-dialog "base/gapless-connect")
        (erc-server-flood-penalty 0.1)
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
index e4db3fc054..6c6568cad6 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-netid-bouncer-id.el --- net-id bouncer ID scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el
index 681e6cd469..f48e1ef394 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-netid-bouncer-recon-base.el --- net-id base scenarios 
-*- lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-both.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-both.el
index 73f9efdc8c..2f58c3269e 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-both.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-both.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-netid-bouncer-recon-both.el --- net-id both scenarios 
-*- lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-id.el
index 132d7eeb03..72510809ab 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-id.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-id.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-netid-bouncer-recon-id.el --- recon ID scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer.el
index bd71e81bb5..d171e1f9f9 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-netid-bouncer.el --- net-id bouncer scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-samenet.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-samenet.el
index 86b0388eec..d028fefd72 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-samenet.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-samenet.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-network-id-samenet.el --- netid-id samenet scenarios 
-*- lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-reconnect.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reconnect.el
index 56b7570c29..06143a7a0c 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-reconnect.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reconnect.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-reconnect.el --- Base-reconnect scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-renick.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-renick.el
index e10aa5cc17..7daf4567b2 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-renick.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-renick.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-renick.el --- Re-nicking scenarios -*- lexical-binding: 
t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-reuse-buffers.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reuse-buffers.el
index fa10e592ec..57b0a29c15 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-reuse-buffers.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reuse-buffers.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-reuse-buffers.el --- base-reuse-buffers scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -42,7 +42,8 @@ collisions involving bouncers in ERC.  Run EXTRA."
                                 :nick "tester"
                                 :password "foonet:changeme"
                                 :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (should (string= (buffer-name)
+                         (format "127.0.0.1:%d/127.0.0.1" port)))
         (erc-d-t-search-for 12 "marked as being away")))
 
     (ert-info ("Connect to barnet")
@@ -51,30 +52,62 @@ collisions involving bouncers in ERC.  Run EXTRA."
                                 :nick "tester"
                                 :password "barnet:changeme"
                                 :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (should (string= (buffer-name)
+                         (format "127.0.0.1:%d/127.0.0.1<2>" port)))
         (erc-d-t-search-for 45 "marked as being away")))
 
-    (erc-d-t-wait-for 2 (get-buffer "foonet"))
-    (erc-d-t-wait-for 2 (get-buffer "barnet"))
+    (erc-d-t-wait-for 2 (get-buffer (format "127.0.0.1:%d/127.0.0.1" port)))
+    (erc-d-t-wait-for 2 (get-buffer (format "127.0.0.1:%d/127.0.0.1<2>" port)))
 
     (ert-info ("Server buffers are unique, no IP-based names")
-      (should-not (eq (get-buffer "foonet") (get-buffer "barnet")))
-      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
-
-    (when more (funcall more))))
+      (should (cdr (erc-scenarios-common-buflist "127.0.0.1"))))
+    (when more (funcall more port))))
 
+;; XXX maybe remove: already covered many times over by other scenarios
 (ert-deftest erc-scenarios-base-reuse-buffers-server-buffers--enabled ()
   :tags '(:expensive-test)
-  (should erc-reuse-buffers)
+  (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+    (should erc-reuse-buffers))
   (let ((erc-scenarios-common-dialog "base/reuse-buffers/server-buffers"))
-    (erc-scenarios-common--base-reuse-buffers-server-buffers)))
-
+    (erc-scenarios-common-with-cleanup
+        ((erc-d-linger-secs 1)
+         (dumb-server (erc-d-run "localhost" t 'foonet 'barnet))
+         (port (process-contact dumb-server :service))
+         erc-autojoin-channels-alist)
+
+      (ert-info ("Connect to foonet")
+        (with-current-buffer (erc :server "127.0.0.1"
+                                  :port port
+                                  :nick "tester"
+                                  :password "foonet:changeme"
+                                  :full-name "tester")
+          (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+          (erc-d-t-search-for 12 "marked as being away")))
+
+      (ert-info ("Connect to barnet")
+        (with-current-buffer (erc :server "127.0.0.1"
+                                  :port port
+                                  :nick "tester"
+                                  :password "barnet:changeme"
+                                  :full-name "tester")
+          (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+          (erc-d-t-search-for 45 "marked as being away")))
+
+      (erc-d-t-wait-for 2 (get-buffer "foonet"))
+      (erc-d-t-wait-for 2 (get-buffer "barnet"))
+
+      (ert-info ("Server buffers are unique, no IP-based names")
+        (should-not (eq (get-buffer "foonet") (get-buffer "barnet")))
+        (should-not (erc-scenarios-common-buflist "127.0.0.1"))))))
+
+;; FIXME no sense in running this twice (JOIN variant includes this)
 (ert-deftest erc-scenarios-base-reuse-buffers-server-buffers--disabled ()
   :tags '(:expensive-test)
-  (should erc-reuse-buffers)
-  (let ((erc-scenarios-common-dialog "base/reuse-buffers/server-buffers")
-        erc-reuse-buffers)
-    (erc-scenarios-common--base-reuse-buffers-server-buffers)))
+  (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+    (should erc-reuse-buffers)
+    (let ((erc-scenarios-common-dialog "base/reuse-buffers/server-buffers")
+          erc-reuse-buffers)
+      (erc-scenarios-common--base-reuse-buffers-server-buffers nil))))
 
 ;; This also asserts that `erc-cmd-JOIN' is no longer susceptible to a
 ;; regression introduced in 28.1 (ERC 5.4) that caused phantom target
@@ -85,50 +118,58 @@ collisions involving bouncers in ERC.  Run EXTRA."
 ;; nil.
 ;;
 ;; Note: All the `erc-get-channel-user' calls have to do with the fact
-;; that `erc-default-target' relies on the less-than-well-defined
-;; `erc-default-recipients' and is thus overloaded in the sense of
-;; being used both for retrieving a target name and checking if
-;; channel has been PARTed.  While not ideal, `erc-get-channel-user'
+;; that `erc-default-target' relies on the ambiguously defined
+;; `erc-default-recipients' (meaning it's overloaded in the sense of
+;; being used both for retrieving a target name and checking if a
+;; channel has been PARTed).  While not ideal, `erc-get-channel-user'
 ;; can (also) be used to detect the latter.
 
-(defun erc-scenarios-common--base-reuse-buffers-channel-buffers ()
+(defun erc-scenarios-common--base-reuse-buffers-channel-buffers (port)
   "The option `erc-reuse-buffers' is still respected when nil.
 Adapted from scenario clash-of-chans/uniquify described in Bug#48598:
 28.0.50; buffer-naming collisions involving bouncers in ERC."
-  (let ((expect (erc-d-t-make-expecter))
-        (server-process-bar (with-current-buffer "barnet" erc-server-process))
-        (server-process-foo (with-current-buffer "foonet" erc-server-process)))
+  (let* ((expect (erc-d-t-make-expecter))
+         (server-buffer-foo
+          (get-buffer (format "127.0.0.1:%d/127.0.0.1" port)))
+         (server-buffer-bar
+          (get-buffer (format "127.0.0.1:%d/127.0.0.1<2>" port)))
+         (chan-buffer-foo (get-buffer "#chan/127.0.0.1"))
+         (chan-buffer-bar (get-buffer "#chan/127.0.0.1<2>"))
+         (server-process-foo (with-current-buffer server-buffer-foo
+                               erc-server-process))
+         (server-process-bar (with-current-buffer server-buffer-bar
+                               erc-server-process)))
 
     (ert-info ("Unique #chan buffers exist")
       (let ((chan-bufs (erc-scenarios-common-buflist "#chan"))
-            (names '("#chan@barnet" "#chan@foonet")))
-        (should (member (buffer-name (pop chan-bufs)) names))
-        (should (member (buffer-name (pop chan-bufs)) names))
+            (known (list chan-buffer-bar chan-buffer-foo)))
+        (should (memq (pop chan-bufs) known))
+        (should (memq (pop chan-bufs) known))
         (should-not chan-bufs)))
 
     (ert-info ("#chan@foonet is exclusive and not contaminated")
-      (with-current-buffer "#chan@foonet"
+      (with-current-buffer chan-buffer-foo
         (funcall expect 1 "<bob>")
         (erc-d-t-absent-for 0.1 "<joe>")
         (funcall expect 1 "strength to climb")
         (should (eq erc-server-process server-process-foo))))
 
     (ert-info ("#chan@barnet is exclusive and not contaminated")
-      (with-current-buffer "#chan@barnet"
+      (with-current-buffer chan-buffer-bar
         (funcall expect 1 "<joe>")
         (erc-d-t-absent-for 0.1 "<bob>")
         (funcall expect 1 "the loudest noise")
         (should (eq erc-server-process server-process-bar))))
 
     (ert-info ("Part #chan@foonet")
-      (with-current-buffer "#chan@foonet"
+      (with-current-buffer chan-buffer-foo
         (erc-d-t-search-for 1 "shake my sword")
         (erc-cmd-PART "#chan")
         (funcall expect 3 "You have left channel #chan")
         (erc-cmd-JOIN "#chan")))
 
     (ert-info ("Part #chan@barnet")
-      (with-current-buffer "#chan@barnet"
+      (with-current-buffer chan-buffer-bar
         (funcall expect 3 "Arm it in rags")
         (should (erc-get-channel-user (erc-current-nick)))
         (erc-cmd-PART "#chan")
@@ -137,12 +178,12 @@ Adapted from scenario clash-of-chans/uniquify described 
in Bug#48598:
         (erc-cmd-JOIN "#chan")))
 
     (erc-d-t-wait-for 3 "New unique target buffer for #chan@foonet created"
-      (get-buffer "#chan@foonet<2>"))
+      (get-buffer "#chan/127.0.0.1<3>"))
 
     (ert-info ("Activity continues in new, <n>-suffixed #chan@foonet buffer")
-      (with-current-buffer "#chan@foonet"
+      (with-current-buffer chan-buffer-foo
         (should-not (erc-get-channel-user (erc-current-nick))))
-      (with-current-buffer "#chan@foonet<2>"
+      (with-current-buffer "#chan/127.0.0.1<3>"
         (should (erc-get-channel-user (erc-current-nick)))
         (funcall expect 2 "You have joined channel #chan")
         (funcall expect 2 "#chan was created on")
@@ -150,13 +191,14 @@ Adapted from scenario clash-of-chans/uniquify described 
in Bug#48598:
         (should (eq erc-server-process server-process-foo))
         (erc-d-t-absent-for 0.2 "<joe>")))
 
-    (erc-d-t-wait-for 3 "New unique target buffer for #chan@barnet created"
-      (get-buffer "#chan@barnet<2>"))
+    (sit-for 3)
+    (erc-d-t-wait-for 5 "New unique target buffer for #chan@barnet created"
+      (get-buffer "#chan/127.0.0.1<4>"))
 
     (ert-info ("Activity continues in new, <n>-suffixed #chan@barnet buffer")
-      (with-current-buffer "#chan@barnet"
+      (with-current-buffer chan-buffer-bar
         (should-not (erc-get-channel-user (erc-current-nick))))
-      (with-current-buffer "#chan@barnet<2>"
+      (with-current-buffer "#chan/127.0.0.1<4>"
         (funcall expect 2 "You have joined channel #chan")
         (funcall expect 1 "Users on #chan: @mike joe tester")
         (funcall expect 2 "<mike>")
@@ -166,24 +208,35 @@ Adapted from scenario clash-of-chans/uniquify described 
in Bug#48598:
     (ert-info ("Two new chans created for a total of four")
       (let* ((bufs (erc-scenarios-common-buflist "#chan"))
              (names (sort (mapcar #'buffer-name bufs) #'string<)))
-        (should (equal names '("#chan@barnet" "#chan@barnet<2>"
-                               "#chan@foonet" "#chan@foonet<2>")))))
+        (should
+         (equal names (mapcar (lambda (f) (concat "#chan/127.0.0.1" f))
+                              '("" "<2>" "<3>" "<4>"))))))
 
     (ert-info ("All output sent")
-      (with-current-buffer "#chan@foonet<2>"
+      (with-current-buffer "#chan/127.0.0.1<3>"
         (while (accept-process-output server-process-foo))
         (funcall expect 3 "most lively"))
-      (with-current-buffer "#chan@barnet<2>"
+      (with-current-buffer "#chan/127.0.0.1<4>"
         (while (accept-process-output server-process-bar))
-        (funcall expect 3 "soul black")))))
+        (funcall expect 3 "soul black")))
+
+    ;; TODO ensure the exact <N>'s aren't reassigned during killing as
+    ;; they are when the option is on.
+    (ert-info ("Buffers are exempt from shortening")
+      (kill-buffer "#chan/127.0.0.1<4>")
+      (kill-buffer "#chan/127.0.0.1<3>")
+      (kill-buffer chan-buffer-bar)
+      (should-not (get-buffer "#chan"))
+      (should chan-buffer-foo))))
 
 (ert-deftest erc-scenarios-base-reuse-buffers-channel-buffers--disabled ()
   :tags '(:expensive-test)
-  (should erc-reuse-buffers)
-  (let ((erc-scenarios-common-dialog "base/reuse-buffers/channel-buffers")
-        (erc-server-flood-penalty 0.1)
-        erc-reuse-buffers)
-    (erc-scenarios-common--base-reuse-buffers-server-buffers
-     #'erc-scenarios-common--base-reuse-buffers-channel-buffers)))
+  (with-suppressed-warnings ((obsolete erc-reuse-buffers))
+    (should erc-reuse-buffers)
+    (let ((erc-scenarios-common-dialog "base/reuse-buffers/channel-buffers")
+          (erc-server-flood-penalty 0.1)
+          erc-reuse-buffers)
+      (erc-scenarios-common--base-reuse-buffers-server-buffers
+       #'erc-scenarios-common--base-reuse-buffers-channel-buffers))))
 
 ;;; erc-scenarios-base-reuse-buffers.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-unstable.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-unstable.el
index 4739ce9eba..e389619ddc 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-unstable.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-unstable.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-base-unstable.el --- base unstable scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-upstream-recon-soju.el
similarity index 53%
copy from test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
copy to test/lisp/erc/erc-scenarios/erc-scenarios-base-upstream-recon-soju.el
index e4db3fc054..a3b2865542 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-upstream-recon-soju.el
@@ -1,6 +1,6 @@
-;;; erc-scenarios-base-netid-bouncer-id.el --- net-id bouncer ID scenarios -*- 
lexical-binding: t -*-
+;;; erc-scenarios-upstream-recon-soju.el --- Upstream soju -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -18,17 +18,25 @@
 ;; along with this program.  If not, see
 ;; <https://www.gnu.org/licenses/>.
 
+;; Commentary:
+;;
+;; These concern the loss and recovery of a proxy's IRC-side connection.
+
 (require 'ert-x)
 (eval-and-compile
   (let ((load-path (cons (ert-resource-directory) load-path)))
     (require 'erc-scenarios-common)))
 
-(ert-deftest erc-scenarios-base-netid-bouncer--id-foo ()
-  :tags '(:expensive-test)
-  (erc-scenarios-common--base-network-id-bouncer '(:foo-id t) 'foonet 'barnet))
-
-(ert-deftest erc-scenarios-base-netid-bouncer--id-bar ()
-  :tags '(:expensive-test)
-  (erc-scenarios-common--base-network-id-bouncer '(:bar-id t) 'foonet 'barnet))
+(ert-deftest erc-scenarios-upstream-recon--soju ()
+  (erc-scenarios-common--upstream-reconnect
+   (lambda ()
+     (with-current-buffer "foonet"
+       (erc-d-t-search-for 1 "disconnected from foonet")
+       (erc-d-t-search-for 1 "connected from foonet"))
+     (with-current-buffer "barnet"
+       (erc-d-t-search-for 1 "disconnected from barnet")
+       (erc-d-t-search-for 1 "connected from barnet")))
+   'soju-foonet
+   'soju-barnet))
 
-;;; erc-scenarios-base-netid-bouncer-id.el ends here
+;;; erc-scenarios-upstream-recon-soju.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-upstream-recon-znc.el
similarity index 54%
copy from test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
copy to test/lisp/erc/erc-scenarios/erc-scenarios-base-upstream-recon-znc.el
index e4db3fc054..f79dca4895 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-upstream-recon-znc.el
@@ -1,6 +1,6 @@
-;;; erc-scenarios-base-netid-bouncer-id.el --- net-id bouncer ID scenarios -*- 
lexical-binding: t -*-
+;;; erc-scenarios-upstream-recon-znc.el --- Upstream znc -*- lexical-binding: 
t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -18,17 +18,25 @@
 ;; along with this program.  If not, see
 ;; <https://www.gnu.org/licenses/>.
 
+;; Commentary:
+;;
+;; These concern the loss and recovery of a proxy's IRC-side connection.
+
 (require 'ert-x)
 (eval-and-compile
   (let ((load-path (cons (ert-resource-directory) load-path)))
     (require 'erc-scenarios-common)))
 
-(ert-deftest erc-scenarios-base-netid-bouncer--id-foo ()
-  :tags '(:expensive-test)
-  (erc-scenarios-common--base-network-id-bouncer '(:foo-id t) 'foonet 'barnet))
-
-(ert-deftest erc-scenarios-base-netid-bouncer--id-bar ()
-  :tags '(:expensive-test)
-  (erc-scenarios-common--base-network-id-bouncer '(:bar-id t) 'foonet 'barnet))
+(ert-deftest erc-scenarios-upstream-recon--znc ()
+  (erc-scenarios-common--upstream-reconnect
+   (lambda ()
+     (with-current-buffer "*status@foonet"
+       (erc-d-t-search-for 1 "Disconnected from IRC")
+       (erc-d-t-search-for 1 "Connected!"))
+     (with-current-buffer "*status@barnet"
+       (erc-d-t-search-for 1 "Disconnected from IRC")
+       (erc-d-t-search-for 1 "Connected!")))
+   'znc-foonet
+   'znc-barnet))
 
-;;; erc-scenarios-base-netid-bouncer-id.el ends here
+;;; erc-scenarios-upstream-recon-znc.el ends here
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-internal.el
similarity index 60%
copy from 
test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el
copy to test/lisp/erc/erc-scenarios/erc-scenarios-internal.el
index 681e6cd469..e4e1edb97e 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-internal.el
@@ -1,6 +1,6 @@
-;;; erc-scenarios-base-netid-bouncer-recon-base.el --- net-id base scenarios 
-*- lexical-binding: t -*-
+;;; erc-scenarios-internal.el --- Proxy file for erc-d tests -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -20,11 +20,8 @@
 
 (require 'ert-x)
 (eval-and-compile
-  (let ((load-path (cons (ert-resource-directory) load-path)))
-    (require 'erc-scenarios-common)))
+  (let ((load-path (cons (expand-file-name "erc-d" (ert-resource-directory))
+                         load-path)))
+    (load "erc-d-tests" nil 'silent)))
 
-(ert-deftest erc-scenarios-base-netid-bouncer--recon-base ()
-  :tags '(:expensive-test)
-  (erc-scenarios-common--base-network-id-bouncer--reconnect nil nil))
-
-;;; erc-scenarios-base-netid-bouncer-recon-base.el ends here
+;;; erc-scenarios-internal.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-auth-source.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-auth-source.el
new file mode 100644
index 0000000000..94336db07c
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-auth-source.el
@@ -0,0 +1,67 @@
+;;; erc-scenarios-join-auth-source.el --- join-auth-source scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2022 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
+;; <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; TODO add another test with autojoin and channel keys
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-join-auth-source--network ()
+  :tags '(:expensive-test)
+  (should erc-auth-source-join-function)
+  (erc-scenarios-common-with-cleanup
+      ((entries
+        '("machine 127.0.0.1 port %d login \"#foo\" password spam"
+          "machine irc.foonet.org port %d login tester password fake"
+          "machine irc.foonet.org login \"#spam\" password secret"
+          "machine foonet port %d login dummy password fake"
+          "machine 127.0.0.1 port %d login dummy password changeme"))
+       (erc-scenarios-common-dialog "join/auth-source")
+       (erc-server-flood-penalty 0.1)
+       (dumb-server (erc-d-run "localhost" t 'foonet))
+       (port (process-contact dumb-server :service))
+       (ents (mapcar (lambda (fmt) (format fmt port)) entries))
+       (netrc-file (make-temp-file "auth-source-test" nil nil
+                                   (string-join ents "\n")))
+       (auth-sources (list netrc-file))
+       (auth-source-do-cache nil)
+       (expect (erc-d-t-make-expecter))
+       (erc-scenarios-common-extra-teardown (lambda ()
+                                              (delete-file netrc-file))))
+
+    (ert-info ("Connect without password")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "dummy"
+                                :full-name "dummy")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (erc-d-t-wait-for 8 (eq erc-network 'foonet))
+        (funcall expect 10 "user modes")
+        (erc-scenarios-common-say "/JOIN #spam")))
+
+    (ert-info ("Join #spam")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
+        (funcall expect 10 "#spam was created on")))))
+
+;;; erc-scenarios-join-auth-source.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd-id.el
index 8b8166f8b1..e2e437321d 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd-id.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd-id.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-join-netid-newcmd-id.el --- join netid newcmd scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd.el
index 2eac8cd523..1a541a46b3 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-join-netid-newcmd.el --- join netid newcmd scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon-id.el
index 14ca1054b3..92bdd643de 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon-id.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon-id.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-join-netid-recon-id.el --- join-netid-recon scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon.el
index be5230ef4a..cbdba07e25 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-join-netid-recon.el --- join-netid-recon scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-misc.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-misc.el
index 22c6ac4fef..f65041c97c 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-misc.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-misc.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-misc.el --- Misc scenarios for ERC -*- lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-services-misc.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-services-misc.el
index 07c8f1ef7a..cb1aa6ff32 100644
--- a/test/lisp/erc/erc-scenarios/erc-scenarios-services-misc.el
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-services-misc.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-services-misc.el --- Services-misc scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted.eld 
b/test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted.eld
index 39bec93901..5c32070d85 100644
--- a/test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted.eld
+++ b/test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted.eld
@@ -27,7 +27,7 @@
  (0 ":irc.foonet.org 353 tester = #chan :alice tester @bob")
  (0 ":irc.foonet.org 366 tester #chan :End of NAMES list"))
 
-((mode 4 "MODE #chan")
+((mode 10 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620104779")
  (0.1 ":bob!~u@rz2v467q4rwhy.irc PRIVMSG #chan :tester, welcome!")
diff --git 
a/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/soju-barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/soju-barnet.eld
new file mode 100644
index 0000000000..3fe2bb71fa
--- /dev/null
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/soju-barnet.eld
@@ -0,0 +1,64 @@
+;; -*- mode: lisp-data; -*-
+((pass 6 "PASS :changeme"))
+((nick 1 "NICK tester"))
+((user 1 "USER tester@vanilla/barnet 0 * :tester")
+ (0.01 ":soju.im 001 tester :Welcome to soju, tester")
+ (0.01 ":soju.im 002 tester :Your host is soju.im")
+ (0.00 ":soju.im 004 tester soju.im soju aiwroO OovaimnqpsrtklbeI")
+ (0.53 ":soju.im 005 tester CHATHISTORY=1000 CASEMAPPING=ascii BOUNCER_NETID=2 
AWAYLEN=390 CHANLIMIT=#:100 INVEX NETWORK=barnet NICKLEN=32 WHOX MODES BOT=B 
ELIST=U MAXLIST=beI:60 :are supported")
+ (0.01 ":soju.im 005 tester TOPICLEN=390 CHANMODES=Ibe,k,fl,CEMRUimnstu 
CHANNELLEN=64 EXCEPTS EXTBAN=,m KICKLEN=390 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 MAXTARGETS=4 MONITOR=100 CHANTYPES=# PREFIX=(qaohv)~&@%+ UTF8ONLY :are 
supported")
+ (0.22 ":soju.im 221 tester +Zi")
+ (0.00 ":soju.im 422 tester :Use /motd to read the message of the day"))
+
+((mode 5 "MODE tester +i")
+ (0.00 ":tester!tester@10.0.2.100 JOIN #chan")
+ (0.06 ":soju.im 353 tester = #chan :tester @mike joe")
+ (0.01 ":soju.im 366 tester #chan :End of /NAMES list")
+ (0.23 ":irc.barnet.org 221 tester +Zi"))
+
+((mode 2.95 "MODE #chan")
+ (0.00 ":soju.im 324 tester #chan +tn")
+ (0.01 ":soju.im 329 tester #chan 1652878846")
+ (0.01 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: There is five in the 
first show.")
+ (0.00 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Sir, I was an inward of 
his. A shy fellow was the duke; and, I believe I know the cause of his 
withdrawing.")
+ (0.00 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Proud of employment, 
willingly I go.")
+ (0.09 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Dull not device by 
coldness and delay.")
+ (0.09 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Our states are forfeit: 
seek not to undo us.")
+ (0.06 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Come, you are too 
severe a moraler. As the time, the place, and the condition of this country 
stands, I could heartily wish this had not befallen, but since it is as it is, 
mend it for your own good.")
+ (0.06 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Who hath upon him still 
that natural stamp.")
+ (0.07 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Arraign her first; 'tis 
Goneril. I here take my oath before this honourable assembly, she kicked the 
poor king her father.")
+ (0.06 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Lady, I will commend 
you to mine own heart.")
+ (0.08 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Look, what I will not, 
that I cannot do.")
+ (0.08 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: That he would wed me, 
or else die my lover.")
+ (0.08 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Come your way, sir. 
Bless you, good father friar.")
+ (0.08 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Under correction, sir, 
we know whereuntil it doth amount.")
+ (0.08 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: For I am nothing if not 
critical.")
+ (0.06 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Once more I'll read the 
ode that I have writ.")
+ (0.06 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: This is the foul fiend 
Flibbertigibbet: he begins at curfew, and walks till the first cock; he gives 
the web and the pin, squints the eye, and makes the harelip; mildews the white 
wheat, and hurts the poor creature of earth.")
+ (0.06 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Sir, I praise the Lord 
for you, and so may my parishioners; for their sons are well tutored by you, 
and their daughters profit very greatly under you: you are a good member of the 
commonwealth.")
+ (0.08 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: If it please your 
honour, I know not well what they are; but precise villains they are, that I am 
sure of, and void of all profanation in the world that good Christians ought to 
have.")
+ ;; Unexpected disconnect
+ (0.03 ":BouncerServ!BouncerServ@BouncerServ NOTICE tester :disconnected from 
barnet: failed to handle messages: failed to read IRC command: read tcp 
[::1]:54990->[::1]:6668: read: software caused connection abort")
+ ;; Eventual reconnect
+ (0.79 ":BouncerServ!BouncerServ@BouncerServ NOTICE tester :connected to 
barnet")
+ ;; No MOTD or other numerics
+ (0.01 ":soju.im 005 tester AWAYLEN=390 BOT=B CHANLIMIT=#:100 
CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# ELIST=U EXCEPTS 
EXTBAN=,m INVEX KICKLEN=390 :are supported")
+ (0.01 ":soju.im 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 
NETWORK=barnet NICKLEN=32 PREFIX=(qaohv)~&@%+ 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 TOPICLEN=390 UTF8ONLY WHOX :are supported")
+ (0.22 ":irc.barnet.org 221 tester +Zi")
+ (0.01 ":irc.barnet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect.")
+ ;; Server-initialed join
+ (0.01 ":tester!tester@10.0.2.100 JOIN #chan"))
+
+((mode 1 "MODE #chan")
+ (0.22 ":soju.im 353 tester = #chan :@mike joe tester")
+ (0.00 ":soju.im 366 tester #chan :End of /NAMES list")
+ (0.00 ":soju.im 324 tester #chan +nt")
+ (0.00 ":soju.im 329 tester #chan 1652878846")
+ (0.00 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :tester, welcome!")
+ (0.00 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :tester, welcome!")
+ (0.06 ":soju.im 324 tester #chan +nt")
+ (0.00 ":soju.im 329 tester #chan 1652878846")
+ (0.62 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Thou art my brother; so 
we'll hold thee ever.")
+ (0.00 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Very well! go to! I 
cannot go to, man; nor 'tis not very well: by this hand, I say, it is very 
scurvy, and begin to find myself fobbed in it.")
+ (0.00 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: The heir of Alen on, 
Katharine her name.")
+ (0.09 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Go to; farewell! put 
money enough in your purse."))
diff --git 
a/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/soju-foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/soju-foonet.eld
new file mode 100644
index 0000000000..63dfcb184c
--- /dev/null
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/soju-foonet.eld
@@ -0,0 +1,72 @@
+;; -*- mode: lisp-data; -*-
+((pass 5 "PASS :changeme"))
+((nick 1 "NICK tester"))
+((user 1 "USER tester@vanilla/foonet 0 * :tester")
+ (0.01 ":soju.im 001 tester :Welcome to soju, tester")
+ (0.02 ":soju.im 002 tester :Your host is soju.im")
+ (0.01 ":soju.im 004 tester soju.im soju aiwroO OovaimnqpsrtklbeI")
+ (0.00 ":soju.im 005 tester CHATHISTORY=1000 CASEMAPPING=ascii BOUNCER_NETID=1 
CHANTYPES=# PREFIX=(qaohv)~&@%+ UTF8ONLY AWAYLEN=390 NICKLEN=32 WHOX 
CHANLIMIT=#:100 INVEX NETWORK=foonet MODES :are supported")
+ (0.00 ":soju.im 005 tester TOPICLEN=390 BOT=B ELIST=U MAXLIST=beI:60 
CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 EXCEPTS EXTBAN=,m KICKLEN=390 MAXTARGETS=4 MONITOR=100 :are supported")
+ (0.00 ":soju.im 221 tester +Zi")
+ (0.00 ":soju.im 422 tester :Use /motd to read the message of the day"))
+
+((mode 5 "MODE tester +i")
+ (0.2 ":irc.foonet.org 221 tester +Zi")
+ (0.0 ":tester!tester@10.0.2.100 JOIN #chan")
+ (0.0 ":soju.im 353 tester = #chan :tester @alice bob")
+ (0.1 ":soju.im 366 tester #chan :End of /NAMES list")
+ (0.0 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: Princely shall be thy 
usage every way.")
+ (0.1 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: Tell me thy reason why 
thou wilt marry."))
+
+((mode 5 "MODE #chan")
+ (0.00 ":soju.im 324 tester #chan +nt")
+ (0.01 ":soju.im 329 tester #chan 1652878847")
+ (0.02 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: There is no leprosy 
but what thou speak'st.")
+ (0.09 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: For I upon this bank 
will rest my head.")
+ (0.01 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: To ruffle in the 
commonwealth of Rome.")
+ (0.08 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: For I can nowhere find 
him like a man.")
+ (0.09 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: Ay, sir; but she will 
none, she gives you thanks.")
+ (0.05 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: That man should be at 
woman's command, and yet no hurt done! Though honesty be no puritan, yet it 
will do no hurt; it will wear the surplice of humility over the black gown of a 
big heart. I am going, forsooth: the business is for Helen to come hither.")
+ (0.07 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: Indeed, I should have 
asked you that before.")
+ (0.09 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: Faith, we met, and 
found the quarrel was upon the seventh cause.")
+ (0.05 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: And then, I hope, thou 
wilt be satisfied.")
+ (0.06 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: Well, I will forget 
the condition of my estate, to rejoice in yours.")
+ (0.05 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: Ah! sirrah, this 
unlook'd-for sport comes well.")
+ (0.01 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: Mayst thou inherit 
too! Welcome to Paris.")
+ (0.04 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: That I would choose, 
were I to choose anew.")
+ (0.08 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: Good Tom Drum, lend me 
a handkercher: so, I thank thee. Wait on me home, I'll make sport with thee: 
let thy curtsies alone, they are scurvy ones.")
+ (0.06 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: Excellent workman! 
Thou canst not paint a man so bad as is thyself.")
+ (0.07 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: That every braggart 
shall be found an ass.")
+ (0.07 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: This is but a custom 
in your tongue; you bear a graver purpose, I hope.")
+ (0.02 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: Well, we will have 
such a prologue, and it shall be written in eight and six.")
+ (0.01 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: Tell me thy reason why 
thou wilt marry.")
+ (0.06 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: According to the 
measure of their states.")
+
+ ;; Unexpected disconnect
+ (0.07 ":BouncerServ!BouncerServ@BouncerServ NOTICE tester :disconnected from 
foonet: failed to handle messages: failed to read IRC command: read tcp 
[::1]:57224->[::1]:6667: read: software caused connection abort")
+ ;; Eventual reconnect
+ (1.02 ":BouncerServ!BouncerServ@BouncerServ NOTICE tester :connected to 
foonet")
+ ;; No MOTD or other numerics
+ (0.01 ":soju.im 005 tester AWAYLEN=390 BOT=B CHANLIMIT=#:100 
CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# ELIST=U EXCEPTS 
EXTBAN=,m INVEX KICKLEN=390 :are supported")
+ (0.02 ":soju.im 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 
NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 TOPICLEN=390 UTF8ONLY WHOX :are supported")
+ (0.02 ":irc.foonet.org 221 tester +Zi")
+ (0.23 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect.")
+ ;; Server-initialed join
+ (0.02 ":tester!tester@10.0.2.100 JOIN #chan"))
+
+((mode 5 "MODE #chan")
+ (0.03 ":soju.im 353 tester = #chan :@alice bob tester")
+ (0.03 ":soju.im 366 tester #chan :End of /NAMES list")
+ (0.00 ":soju.im 324 tester #chan +nt")
+ (0.00 ":soju.im 329 tester #chan 1652878847")
+ (0.00 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :tester, welcome!")
+ (0.00 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :tester, welcome!")
+ (0.46 ":soju.im 324 tester #chan +nt")
+ (0.01 ":soju.im 329 tester #chan 1652878847")
+ (0.00 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: Thou desirest me to 
stop in my tale against the hair.")
+ (0.07 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: But my intents are 
fix'd and will not leave me.")
+ (0.09 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: That last is true; the 
sweeter rest was mine.")
+ (0.09 ":alice!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :bob: No matter whither, so 
you come not here.")
+ (0.09 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: My lord, in heart; and 
let the health go round."))
+
+((linger 12 LINGER))
diff --git 
a/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/znc-barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/znc-barnet.eld
new file mode 100644
index 0000000000..bf5c2b5a74
--- /dev/null
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/znc-barnet.eld
@@ -0,0 +1,93 @@
+;; -*- mode: lisp-data; -*-
+((pass 6 "PASS :changeme"))
+((nick 1 "NICK tester"))
+((user 1 "USER tester@vanilla/barnet 0 * :tester")
+ (0.00 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
+ (0.01 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running 
version ergo-v2.8.0")
+ (0.01 ":irc.barnet.org 003 tester :This server was created Thu, 19 May 2022 
05:33:02 UTC")
+ (0.00 ":irc.barnet.org 004 tester irc.barnet.org ergo-v2.8.0 BERTZios 
CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0.00 ":irc.barnet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii 
CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# 
ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this 
server")
+ (0.01 ":irc.barnet.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES 
MONITOR=100 NETWORK=barnet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server")
+ (0.01 ":irc.barnet.org 005 tester draft/CHATHISTORY=100 :are supported by 
this server")
+ (0.00 ":irc.barnet.org 251 tester :There are 0 users and 3 invisible on 1 
server(s)")
+ (0.00 ":irc.barnet.org 252 tester 0 :IRC Operators online")
+ (0.11 ":irc.barnet.org 254 tester 1 :channels formed")
+ (0.00 ":irc.barnet.org 255 tester :I have 3 clients and 0 servers")
+ (0.00 ":irc.barnet.org 265 tester 3 3 :Current local users 3, max 3")
+ (0.00 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
+ (0.00 ":irc.barnet.org 422 tester :MOTD File is missing"))
+
+((mode 5 "MODE tester +i")
+ (0.0 ":tester!~u@fsr9fwzfeeybc.irc JOIN #chan")
+ (0.05 ":irc.barnet.org 353 tester = #chan :@joe mike tester")
+ (0.01 ":irc.barnet.org 366 tester #chan :End of /NAMES list.")
+ (0.0 ":***!znc@znc.in PRIVMSG #chan :Buffer Playback...")
+ (0.0 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:13] mike: But send the 
midwife presently to me.")
+ (0.01 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:18] joe: Alas! poor 
rogue, I think, i' faith, she loves me.")
+ (0.01 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:20] mike: They did not 
bless us with one happy word.")
+ (0.01 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:24] joe: And hear the 
sentence of your moved prince.")
+ (0.21 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:29] mike: Swear me to 
this, and I will ne'er say no.")
+ (0.01 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:32] joe: As they had 
seen me with these hangman's hands.")
+ (0.01 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:34] mike: Boyet, 
prepare: I will away to-night.")
+ (0.01 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :[05:48:36] joe: For being a 
little bad: so may my husband.")
+ (0.04 ":***!znc@znc.in PRIVMSG #chan :Playback Complete.")
+ (0.0 ":irc.barnet.org 221 tester +Zi")
+ (2.55 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: And whirl along with 
thee about the globe."))
+
+((mode 5 "MODE #chan")
+ (0.00 ":irc.barnet.org 324 tester #chan +nt")
+ (0.00 ":irc.barnet.org 329 tester #chan 1652938384")
+ (0.06 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Unless good-counsel may 
the cause remove.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: Thyself domestic 
officers thine enemy.")
+ (0.01 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Go after her: she's 
desperate; govern her.")
+ (0.30 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: Or else to heaven she 
heaves them for revenge.")
+ (0.01 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Keep up your bright 
swords, for the dew will rust them.")
+ (0.04 ":*status!znc@znc.in PRIVMSG tester :Disconnected from IRC (Connection 
aborted). Reconnecting...")
+ (0.41 ":*status!znc@znc.in PRIVMSG tester :Disconnected from IRC. 
Reconnecting...")
+ (0.59 ":*status!znc@znc.in PRIVMSG tester :Connected!")
+ (0.02 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
+ (0.01 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running 
version ergo-v2.8.0")
+ (0.01 ":irc.barnet.org 003 tester :This server was created Thu, 19 May 2022 
05:33:02 UTC")
+ (0.01 ":irc.barnet.org 004 tester irc.barnet.org ergo-v2.8.0 BERTZios 
CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0.01 ":irc.barnet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii 
CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# 
ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this 
server")
+ (0.01 ":irc.barnet.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES 
MONITOR=100 NETWORK=barnet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server")
+ (0.22 ":irc.barnet.org 005 tester draft/CHATHISTORY=100 :are supported by 
this server")
+ (0.00 ":irc.barnet.org 251 tester :There are 0 users and 3 invisible on 1 
server(s)")
+ (0.01 ":irc.barnet.org 252 tester 0 :IRC Operators online")
+ (0.00 ":irc.barnet.org 253 tester 0 :unregistered connections")
+ (0.00 ":irc.barnet.org 254 tester 1 :channels formed")
+ (0.00 ":irc.barnet.org 255 tester :I have 3 clients and 0 servers")
+ (0.00 ":irc.barnet.org 265 tester 3 3 :Current local users 3, max 3")
+ (0.17 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
+ (0.00 ":irc.barnet.org 422 tester :MOTD File is missing")
+ (0.01 ":irc.barnet.org 221 tester +Zi")
+ (0.00 ":irc.barnet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect.")
+ (0.05 ":irc.barnet.org 352 tester * ~u fsr9fwzfeeybc.irc irc.barnet.org 
tester H :0 ZNC - https://znc.in";)
+ (0.02 ":irc.barnet.org 315 tester tester!*@* :End of WHO list")
+ (0.08 ":tester!~u@fsr9fwzfeeybc.irc JOIN #chan"))
+
+((mode 5 "MODE #chan")
+ (0.05 ":irc.barnet.org 353 tester = #chan :mike tester @joe")
+ (0.01 ":irc.barnet.org 366 tester #chan :End of NAMES list")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :tester, welcome!")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :tester, welcome!")
+ (0.02 ":irc.barnet.org 324 tester #chan +nt")
+ (0.01 ":irc.barnet.org 329 tester #chan 1652938384")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: See, here he comes, and 
I must ply my theme.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Confine yourself but in 
a patient list.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: And bide the penance of 
each three years' day.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Bid me farewell, and 
let me hear thee going.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: Nor shall not, if I do 
as I intend.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Our corn's to reap, for 
yet our tithe's to sow.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: And almost broke my 
heart with extreme laughter.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Of modern seeming do 
prefer against him.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: Like humble-visag'd 
suitors, his high will.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: But yet, poor Claudio! 
There's no remedy.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: Let him make treble 
satisfaction.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: He's that he is; I may 
not breathe my censure.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: To check their folly, 
passion's solemn tears.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: Villain, I have done 
thy mother.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: Please you, therefore, 
draw nigh, and take your places.")
+ (0.00 ":mike!~u@6t6jcije78we2.irc PRIVMSG #chan :joe: You shall not be 
admitted to his sight.")
+ (0.00 ":joe!~u@6t6jcije78we2.irc PRIVMSG #chan :mike: Sir, you shall present 
before her the Nine Worthies. Sir Nathaniel, as concerning some entertainment 
of time, some show in the posterior of this day, to be rendered by our 
assistance, at the king's command, and this most gallant, illustrate, and 
learned gentleman, before the princess; I say, none so fit as to present the 
Nine Worthies.")
+ (0.00 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: Go to; farewell! put 
money enough in your purse."))
diff --git 
a/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/znc-foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/znc-foonet.eld
new file mode 100644
index 0000000000..39c2950aa0
--- /dev/null
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/upstream-reconnect/znc-foonet.eld
@@ -0,0 +1,86 @@
+;; -*- mode: lisp-data; -*-
+((pass 6 "PASS :changeme"))
+((nick 1 "NICK tester"))
+((user 1 "USER tester@vanilla/foonet 0 * :tester")
+ (0.16 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
+ (0.00 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running 
version ergo-v2.8.0")
+ (0.00 ":irc.foonet.org 003 tester :This server was created Thu, 19 May 2022 
05:33:02 UTC")
+ (0.00 ":irc.foonet.org 004 tester irc.foonet.org ergo-v2.8.0 BERTZios 
CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0.00 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii 
CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# 
ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this 
server")
+ (0.01 ":irc.foonet.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES 
MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server")
+ (0.01 ":irc.foonet.org 005 tester draft/CHATHISTORY=100 :are supported by 
this server")
+ (0.00 ":irc.foonet.org 251 tester :There are 0 users and 3 invisible on 1 
server(s)")
+ (0.00 ":irc.foonet.org 252 tester 0 :IRC Operators online")
+ (0.00 ":irc.foonet.org 254 tester 1 :channels formed")
+ (0.00 ":irc.foonet.org 255 tester :I have 3 clients and 0 servers")
+ (0.00 ":irc.foonet.org 265 tester 3 3 :Current local users 3, max 3")
+ (0.00 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
+ (0.00 ":irc.foonet.org 422 tester :MOTD File is missing"))
+
+((mode 6 "MODE tester +i")
+ (0.00 ":tester!~u@rmtvrz9zcwbdq.irc JOIN #chan")
+ (0.09 ":irc.foonet.org 353 tester = #chan :@alice bob tester")
+ (0.00 ":irc.foonet.org 366 tester #chan :End of /NAMES list.")
+ (0.00 ":***!znc@znc.in PRIVMSG #chan :Buffer Playback...")
+ (0.00 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:11] alice: And be 
aveng'd on cursed Tamora.")
+ (0.00 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:13] bob: The 
stronger part of it by her own letters, which make her story true, even to the 
point of her death: her death itself, which could not be her office to say is 
come, was faithfully confirmed by the rector of the place.")
+ (0.01 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:15] alice: The ape is 
dead, and I must conjure him.")
+ (0.00 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:17] bob: Not so; but 
I answer you right painted cloth, from whence you have studied your questions.")
+ (0.01 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:21] alice: The valiant 
Paris seeks you for his love.")
+ (0.00 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:26] bob: To prison 
with her; and away with him.")
+ (0.00 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:30] alice: Tell them 
there I have gold; look, so I have.")
+ (0.00 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :[05:48:35] bob: Will even 
weigh, and both as light as tales.")
+ (0.00 ":***!znc@znc.in PRIVMSG #chan :Playback Complete.")
+ (0.00 ":irc.foonet.org 221 tester +Zi")
+ (0.08 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :alice: By some vile forfeit 
of untimely death."))
+
+((mode 3.51 "MODE #chan")
+ (0.1 ":irc.foonet.org 324 tester #chan +nt")
+ (0.0 ":irc.foonet.org 329 tester #chan 1652938384")
+ (0.0 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :bob: What does this knave 
here ? Get you gone, sirrah: the complaints I have heard of you I do not all 
believe: 'tis my slowness that I do not; for I know you lack not folly to 
commit them, and have ability enough to make such knaveries yours.")
+ (0.0 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :alice: When sects and factions 
were newly born.")
+ (0.1 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :bob: Fall, when Love please! 
marry, to each, but one.")
+ (0.1 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :alice: For I ne'er saw true 
beauty till this night.")
+ (0.1 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :bob: Or say, sweet love, 
what thou desir'st to eat.")
+ (0.1 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :alice: Yes, and will nobly him 
remunerate.")
+ (0.1 ":*status!znc@znc.in PRIVMSG tester :Disconnected from IRC (Connection 
aborted). Reconnecting...")
+ (0.4 ":*status!znc@znc.in PRIVMSG tester :Disconnected from IRC. 
Reconnecting...")
+ (0.9 ":*status!znc@znc.in PRIVMSG tester :Connected!")
+ (0.0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
+ (0.0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running 
version ergo-v2.8.0")
+ (0.0 ":irc.foonet.org 003 tester :This server was created Thu, 19 May 2022 
05:33:02 UTC")
+ (0.0 ":irc.foonet.org 004 tester irc.foonet.org ergo-v2.8.0 BERTZios 
CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0.1 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii 
CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# 
ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this 
server")
+ (0.0 ":irc.foonet.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES 
MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server")
+ (0.0 ":irc.foonet.org 005 tester draft/CHATHISTORY=100 :are supported by this 
server")
+ (0.0 ":irc.foonet.org 251 tester :There are 0 users and 3 invisible on 1 
server(s)")
+ (0.0 ":irc.foonet.org 252 tester 0 :IRC Operators online")
+ (0.0 ":irc.foonet.org 253 tester 0 :unregistered connections")
+ (0.1 ":irc.foonet.org 254 tester 1 :channels formed")
+ (0.0 ":irc.foonet.org 255 tester :I have 3 clients and 0 servers")
+ (0.0 ":irc.foonet.org 265 tester 3 3 :Current local users 3, max 3")
+ (0.0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
+ (0.0 ":irc.foonet.org 422 tester :MOTD File is missing")
+ (0.0 ":irc.foonet.org 221 tester +Zi")
+ (0.0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect.")
+ (0.6 ":irc.foonet.org 352 tester * ~u rmtvrz9zcwbdq.irc irc.foonet.org tester 
H :0 ZNC - https://znc.in";)
+ (0.0 ":irc.foonet.org 315 tester tester!*@* :End of WHO list")
+ (0.0 ":tester!~u@rmtvrz9zcwbdq.irc JOIN #chan"))
+
+((mode 6 "MODE #chan")
+ (0.0 ":irc.foonet.org 353 tester = #chan :@alice bob tester")
+ (0.0 ":irc.foonet.org 366 tester #chan :End of NAMES list")
+ (0.0 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :tester, welcome!")
+ (0.0 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :tester, welcome!")
+ (0.0 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :bob: Being of no power to 
make his wishes good.")
+ (0.0 ":irc.foonet.org 324 tester #chan +nt")
+ (0.0 ":irc.foonet.org 329 tester #chan 1652938384")
+ (0.0 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :alice: In everything I wait 
upon his will.")
+ (0.0 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :bob: Make choice of which 
your highness will see first.")
+ (0.0 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :alice: We waste our lights in 
vain, like lamps by day.")
+ (0.0 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :bob: No, I know that; but it 
is fit I should commit offence to my inferiors.")
+ (0.1 ":bob!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :alice: By my head, here come 
the Capulets.")
+ (0.0 ":alice!~u@rmtvrz9zcwbdq.irc PRIVMSG #chan :bob: Well, I will forget the 
condition of my estate, to rejoice in yours.")
+ (0.0 ":bob!~u@h35cf3bf7rbt4.irc PRIVMSG #chan :alice: My lord, in heart; and 
let the health go round."))
+
+((linger 12 LINGER))
diff --git a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-i.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-i.el
index 1713c4aa8e..83bf1bc71a 100644
--- a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-i.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-i.el
@@ -1,6 +1,6 @@
 ;;; erc-d-i.el --- IRC helpers for ERC test server -*- lexical-binding: t -*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-t.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-t.el
index eebb0f1e2c..a1a7e7e88d 100644
--- a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-t.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-t.el
@@ -1,6 +1,6 @@
 ;;; erc-d-t.el --- ERT helpers for ERC test server -*- lexical-binding: t -*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/erc-d-self.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-tests.el
similarity index 92%
rename from test/lisp/erc/erc-scenarios/erc-d-self.el
rename to test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-tests.el
index c6e6d33bd2..1e6db3a921 100644
--- a/test/lisp/erc/erc-scenarios/erc-d-self.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-tests.el
@@ -1,6 +1,6 @@
-;;; erc-d-self.el --- tests for erc-d -*- lexical-binding: t -*-
+;;; erc-d-tests.el --- tests for erc-d -*- lexical-binding: t -*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -19,30 +19,17 @@
 ;; <https://www.gnu.org/licenses/>.
 
 ;;; Commentary:
-;;
-;; This file tests the dumb server itself.  The file name does not end
-;; in "-tests.el" because test/Makefile looks for corresponding
-;; library files and raises an error when one isn't found.
 
 ;;; Code:
 (require 'ert-x)
 (eval-and-compile
-  (let ((load-path (cons (expand-file-name "erc-d" (ert-resource-directory))
+  (let ((load-path (cons (expand-file-name ".." (ert-resource-directory))
                          load-path)))
     (require 'erc-d)
     (require 'erc-d-t)))
 
 (require 'erc)
 
-;; For now, we need these tests to work in two different layouts:
-;; inside emacs.git, under test/lisp/erc/erc-scenarios, and as part of
-;; a standalone erc-d library.
-
-(defvar erc-d-self--resources-directory
-  (let ((default-directory (ert-resource-directory)))
-    (expand-file-name
-     (if (file-readable-p "erc-d/resources") "erc-d/resources" ""))))
-
 (ert-deftest erc-d-u--canned-load-dialog--basic ()
   (should-not (get-buffer "basic.eld"))
   (should-not erc-d-u--canned-buffers)
@@ -69,7 +56,7 @@
   (should-not (get-buffer "basic.eld"))
   (should-not erc-d-u--canned-buffers))
 
-(defun erc-d-self--make-hunk-reader (hunks)
+(defun erc-d-tests--make-hunk-reader (hunks)
   (let ((p (erc-d-u--read-dialog hunks)))
     (lambda () (erc-d-u--read-exchange p))))
 
@@ -78,11 +65,11 @@
   (should-not (get-buffer "basic.eld"))
   (should-not erc-d-u--canned-buffers)
   (let* ((exes (erc-d-u--canned-load-dialog 'basic))
-         (pass (erc-d-self--make-hunk-reader exes))
-         (nick (erc-d-self--make-hunk-reader exes))
-         (user (erc-d-self--make-hunk-reader exes))
-         (modu (erc-d-self--make-hunk-reader exes))
-         (modc (erc-d-self--make-hunk-reader exes)))
+         (pass (erc-d-tests--make-hunk-reader exes))
+         (nick (erc-d-tests--make-hunk-reader exes))
+         (user (erc-d-tests--make-hunk-reader exes))
+         (modu (erc-d-tests--make-hunk-reader exes))
+         (modc (erc-d-tests--make-hunk-reader exes)))
 
     (should (equal (funcall user) '(user 0.2 "USER user 0 * :tester")))
     (should (equal (funcall modu) '(mode-user 1.2 "MODE tester +i")))
@@ -207,7 +194,7 @@
       (ring-insert-at-beginning ring (make-erc-d-exchange :tag 'bar))
       (should (erc-d--active-ex-p ring)))))
 
-(defun erc-d-self--parse-message-upstream (raw)
+(defun erc-d-tests--parse-message-upstream (raw)
   "Hack shim for parsing RAW line recvd from peer."
   (cl-letf (((symbol-function #'erc-handle-parsed-server-response)
              (lambda (_ p) p)))
@@ -233,7 +220,7 @@
 (ert-deftest erc-d-i--parse-message ()
   (let* ((raw (concat "@time=2020-11-23T09:10:33.088Z "
                       ":tilde.chat BATCH +1 chathistory :#meta"))
-         (upstream (erc-d-self--parse-message-upstream raw))
+         (upstream (erc-d-tests--parse-message-upstream raw))
          (ours (erc-d-i--parse-message raw)))
 
     (ert-info ("Baseline upstream")
@@ -292,7 +279,7 @@
   (let* ((data (with-temp-buffer
                  (insert-file-contents
                   (expand-file-name "irc-parser-tests.eld"
-                                    erc-d-self--resources-directory))
+                                    (ert-resource-directory)))
                  (read (current-buffer))))
          (tests (assoc-default 'tests (assoc-default 'msg-split data)))
          input atoms m ours)
@@ -322,7 +309,7 @@
             (should (equal ours params))
           (should-not ours))))))
 
-(defun erc-d-self--new-ex (existing raw-hunk)
+(defun erc-d-tests--new-ex (existing raw-hunk)
   (let* ((f (lambda (_) (pop raw-hunk)))
          (sd (make-erc-d-u-scan-d :f f)))
     (setf (erc-d-exchange-hunk existing) (make-erc-d-u-scan-e :sd sd)
@@ -337,7 +324,7 @@
                                             (f . ,(lambda () "3"))
                                             (i . emacs-pid))))
          (exchange (make-erc-d-exchange :dialog dialog))
-         (mex (apply-partially #'erc-d-self--new-ex exchange))
+         (mex (apply-partially #'erc-d-tests--new-ex exchange))
          it)
 
     (erc-d-exchange-reload dialog exchange)
@@ -405,7 +392,7 @@
                                         :request "foo bar baz"
                                         ;;            11  222
                                         :match-data '(4 11 4 6 8 11)))
-         (mex (apply-partially #'erc-d-self--new-ex exchange))
+         (mex (apply-partially #'erc-d-tests--new-ex exchange))
          it)
 
     (erc-d-exchange-reload dialog exchange)
@@ -457,7 +444,7 @@
                  (cons 'k (lambda () "abc"))))
          (dialog (make-erc-d-dialog :vars alist))
          (exchange (make-erc-d-exchange :dialog dialog))
-         (mex (apply-partially #'erc-d-self--new-ex exchange))
+         (mex (apply-partially #'erc-d-tests--new-ex exchange))
          it)
 
     (erc-d-exchange-reload dialog exchange)
@@ -536,6 +523,7 @@
     (kill-buffer "*foo*")))
 
 (ert-deftest erc-d-t-wait-for ()
+  :tags '(:unstable)
   (let (v)
     (run-at-time 0.2 nil (lambda () (setq v t)))
     (should (erc-d-t-wait-for 0.4 "result becomes non-nil" v))
@@ -546,15 +534,15 @@
     (setq v nil)
     (should-error (erc-d-t-wait-for -0.4 "inverted becomes non-nil" v))))
 
-(defvar erc-d-self-with-server-password "changeme")
+(defvar erc-d-tests-with-server-password "changeme")
 
 ;; Compromise between removing `autojoin' from `erc-modules' entirely
 ;; and allowing side effects to meddle excessively
 (defvar erc-autojoin-channels-alist)
 
 ;; This is only meant to be used by tests in this file.
-(cl-defmacro erc-d-self-with-server ((dumb-server-var erc-server-buffer-var)
-                                     dialog &rest body)
+(cl-defmacro erc-d-tests-with-server ((dumb-server-var erc-server-buffer-var)
+                                      dialog &rest body)
   "Create server for DIALOG and run BODY.
 DIALOG may also be a list of dialogs.  ERC-SERVER-BUFFER-VAR and
 DUMB-SERVER-VAR are bound accordingly in BODY."
@@ -582,7 +570,7 @@ DUMB-SERVER-VAR are bound accordingly in BODY."
        (erc-toggle-debug-irc-protocol))
      (setq ,erc-server-buffer-var
            (erc :server "localhost"
-                :password erc-d-self-with-server-password
+                :password erc-d-tests-with-server-password
                 :port (process-contact ,dumb-server-var :service)
                 :nick "tester"
                 :full-name "tester"))
@@ -599,7 +587,7 @@ DUMB-SERVER-VAR are bound accordingly in BODY."
          (kill-buffer ,erc-server-buffer-var)
          (erc-d-t-kill-related-buffers)))))
 
-(defmacro erc-d-self-with-failure-spy (found func-syms &rest body)
+(defmacro erc-d-tests-with-failure-spy (found func-syms &rest body)
   "Wrap functions with advice for inspecting errors caused by BODY.
 Do this for functions whose names appear in FUNC-SYMS.  When running
 advice code, add errors to list FOUND.  Note: the teardown finalizer is
@@ -654,7 +642,7 @@ nonzero for this to work."
       (kill-buffer dumb-server-buffer))))
 
 (ert-deftest erc-d-run-basic ()
-  (erc-d-self-with-server (_ _) basic
+  (erc-d-tests-with-server (_ _) basic
     (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
       (erc-d-t-search-for 2 "hey"))
     (when noninteractive
@@ -662,7 +650,7 @@ nonzero for this to work."
 
 (ert-deftest erc-d-run-eof ()
   (skip-unless noninteractive)
-  (erc-d-self-with-server (_ erc-s-buf) eof
+  (erc-d-tests-with-server (_ erc-s-buf) eof
     (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
       (erc-d-t-search-for 2 "hey"))
     (with-current-buffer erc-s-buf
@@ -670,8 +658,8 @@ nonzero for this to work."
 
 (ert-deftest erc-d-run-eof-fail ()
   (let (errors)
-    (erc-d-self-with-failure-spy errors '(erc-d--teardown)
-      (erc-d-self-with-server (_ _) eof
+    (erc-d-tests-with-failure-spy errors '(erc-d--teardown)
+      (erc-d-tests-with-server (_ _) eof
         (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
           (erc-d-t-search-for 2 "hey"))
         (erc-d-t-wait-for 10 errors)))
@@ -679,8 +667,8 @@ nonzero for this to work."
                             (cadr (pop errors))))))
 
 (ert-deftest erc-d-run-linger ()
-  (erc-d-self-with-server (dumb-s _) linger
-    (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
+  (erc-d-tests-with-server (dumb-s _) linger
+    (with-current-buffer (erc-d-t-wait-for 6 (get-buffer "#chan"))
       (erc-d-t-search-for 2 "hey"))
     (with-current-buffer (process-buffer dumb-s)
       (erc-d-t-search-for 2 "Lingering for 1.00 seconds"))
@@ -690,10 +678,10 @@ nonzero for this to work."
 (ert-deftest erc-d-run-linger-fail ()
   (let ((erc-server-flood-penalty 0.1)
         errors)
-    (erc-d-self-with-failure-spy
+    (erc-d-tests-with-failure-spy
         errors '(erc-d--teardown erc-d-command)
-      (erc-d-self-with-server (_ _) linger
-        (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
+      (erc-d-tests-with-server (_ _) linger
+        (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
           (erc-d-t-search-for 2 "hey")
           (erc-cmd-MSG "#chan hi"))
         (erc-d-t-wait-for 10 "Bad match" errors)))
@@ -732,6 +720,7 @@ nonzero for this to work."
       (kill-buffer dumb-server-buffer))))
 
 (ert-deftest erc-d-run-drop-direct ()
+  :tags '(:unstable)
   (let* ((dumb-server (erc-d-run "localhost" t 'drop-a 'drop-b))
          (port (process-contact dumb-server :service))
          (dumb-server-buffer (get-buffer "*erc-d-server*"))
@@ -766,8 +755,8 @@ nonzero for this to work."
   (let ((erc-d-linger-secs 1)
         erc-server-auto-reconnect
         errors)
-    (erc-d-self-with-failure-spy errors '(erc-d--teardown erc-d-command)
-      (erc-d-self-with-server (_ erc-server-buffer) no-match
+    (erc-d-tests-with-failure-spy errors '(erc-d--teardown erc-d-command)
+      (erc-d-tests-with-server (_ erc-server-buffer) no-match
         (with-current-buffer erc-server-buffer
           (erc-d-t-search-for 2 "away")
           (erc-cmd-JOIN "#foo")
@@ -778,8 +767,8 @@ nonzero for this to work."
 (ert-deftest erc-d-run-timeout ()
   (let ((erc-d-linger-secs 1)
         err errors)
-    (erc-d-self-with-failure-spy errors '(erc-d--teardown)
-      (erc-d-self-with-server (_ _) timeout
+    (erc-d-tests-with-failure-spy errors '(erc-d--teardown)
+      (erc-d-tests-with-server (_ _) timeout
         (erc-d-t-wait-for 10 "error caught" errors)))
     (setq err (pop errors))
     (should (eq (car err) 'erc-d-timeout))
@@ -788,9 +777,9 @@ nonzero for this to work."
 (ert-deftest erc-d-run-unexpected ()
   (let ((erc-d-linger-secs 2)
         errors)
-    (erc-d-self-with-failure-spy
+    (erc-d-tests-with-failure-spy
         errors '(erc-d--teardown erc-d-command)
-      (erc-d-self-with-server (_ _) unexpected
+      (erc-d-tests-with-server (_ _) unexpected
         (ert-info ("All specs consumed when more input arrives")
           (erc-d-t-wait-for 10 "error caught" (cdr errors)))))
     (should (string-match-p "unexpected.*MODE" (cadr (pop errors))))
@@ -801,7 +790,7 @@ nonzero for this to work."
 (ert-deftest erc-d-run-unexpected-depleted ()
   (let ((erc-d-linger-secs 3)
         errors)
-    (erc-d-self-with-failure-spy errors '(erc-d--teardown erc-d-command)
+    (erc-d-tests-with-failure-spy errors '(erc-d--teardown erc-d-command)
       (let* ((dumb-server-buffer (get-buffer-create "*erc-d-server*"))
              (dumb-server (erc-d-run "localhost" t 'depleted))
              (expect (erc-d-t-make-expecter))
@@ -818,7 +807,7 @@ nonzero for this to work."
                            :service (process-contact dumb-server :service)
                            :host "localhost"))
         (with-current-buffer dumb-server-buffer
-          (funcall expect 3 "Connection"))
+          (funcall expect 3 "open from"))
         (process-send-string client-proc "PASS :changeme\r\n")
         (sleep-for 0.01)
         (process-send-string client-proc "NICK tester\r\n")
@@ -841,14 +830,14 @@ nonzero for this to work."
     (should (string-match-p "unexpected.*BLAH" (cadr (pop errors))))
     (should-not errors)))
 
-(defun erc-d-self--dynamic-match-user (_dialog exchange)
+(defun erc-d-tests--dynamic-match-user (_dialog exchange)
   "Shared pattern/response handler for canned dynamic DIALOG test."
   (should (string= (match-string 1 (erc-d-exchange-request exchange))
                    "tester")))
 
-(defun erc-d-self--run-dynamic ()
+(defun erc-d-tests--run-dynamic ()
   "Perform common assertions for \"dynamic\" dialog."
-  (erc-d-self-with-server (dumb-server erc-server-buffer) dynamic
+  (erc-d-tests-with-server (dumb-server erc-server-buffer) dynamic
     (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
       (erc-d-t-search-for 2 "tester: hey"))
     (with-current-buffer erc-server-buffer
@@ -875,12 +864,12 @@ nonzero for this to work."
           (list :user (lambda (d e)
                         (erc-d-exchange-rebind d e 'nick nick)
                         (erc-d-exchange-rebind d e 'dom dom)
-                        (erc-d-self--dynamic-match-user d e))
+                        (erc-d-tests--dynamic-match-user d e))
                 :mode-user (lambda (d e)
                              (erc-d-exchange-rebind d e 'nick "tester")
                              (erc-d-exchange-rebind d e 'dom dom))))
          (erc-d-server-fqdn "irc.fsf.org"))
-    (erc-d-self--run-dynamic)
+    (erc-d-tests--run-dynamic)
     (should (equal '((dom . match-user) (nick . match-user) (dom . match-user))
                    dynamic-tally))))
 
@@ -903,13 +892,13 @@ nonzero for this to work."
                    (lambda ()
                      (push 'bind-dom tally)
                      (erc-d-exchange-rebind d e 'dom erc-d-server-fqdn)))
-                  (erc-d-self--dynamic-match-user d e))
+                  (erc-d-tests--dynamic-match-user d e))
                 :mode-user
                 (lambda (d e)
                   (erc-d-exchange-rebind d e 'nick "tester")
                   (erc-d-exchange-rebind d e 'dom erc-d-server-fqdn))))
          (erc-d-server-fqdn "irc.fsf.org"))
-    (erc-d-self--run-dynamic)
+    (erc-d-tests--run-dynamic)
     (should (equal '(bind-nick bind-dom) tally))))
 
 (ert-deftest erc-d-run-dynamic-runtime-stub ()
@@ -917,8 +906,8 @@ nonzero for this to work."
         (erc-d-match-handlers
          (list :pass (lambda (d _e)
                        (erc-d-load-replacement-dialog d 'dynamic-foonet))))
-        (erc-d-self-with-server-password "foonet:changeme"))
-    (erc-d-self-with-server (_ erc-server-buffer)
+        (erc-d-tests-with-server-password "foonet:changeme"))
+    (erc-d-tests-with-server (_ erc-server-buffer)
         (dynamic-stub dynamic-foonet)
       (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
         (erc-d-t-search-for 2 "alice:")
@@ -936,8 +925,8 @@ nonzero for this to work."
          (list :pass (lambda (d _e)
                        (erc-d-load-replacement-dialog
                         d 'dynamic-barnet 1))))
-        (erc-d-self-with-server-password "barnet:changeme"))
-    (erc-d-self-with-server (_ erc-server-buffer)
+        (erc-d-tests-with-server-password "barnet:changeme"))
+    (erc-d-tests-with-server (_ erc-server-buffer)
         (dynamic-stub dynamic-barnet)
       (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
         (erc-d-t-search-for 2 "joe:")
@@ -1045,7 +1034,7 @@ nonzero for this to work."
         (erc-d-tmpl-vars
          `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t)))))
         erc-server-auto-reconnect)
-    (erc-d-self-with-server (_ erc-server-buffer) fuzzy
+    (erc-d-tests-with-server (_ erc-server-buffer) fuzzy
       (with-current-buffer erc-server-buffer
         (erc-d-t-search-for 2 "away")
         (goto-char erc-input-marker)
@@ -1064,7 +1053,7 @@ nonzero for this to work."
         (erc-d-linger-secs 1.2)
         (expect (erc-d-t-make-expecter))
         erc-server-auto-reconnect)
-    (erc-d-self-with-server (_ erc-server-buffer) no-block
+    (erc-d-tests-with-server (_ erc-server-buffer) no-block
       (with-current-buffer erc-server-buffer
         (funcall expect 2 "away")
         (funcall expect 1 erc-prompt)
@@ -1085,7 +1074,7 @@ nonzero for this to work."
           (should-not (search-forward "<bob> I am heard" nil t))
           (funcall expect 1.5 "<bob> I am heard"))))))
 
-(defun erc-d-self--run-proxy-direct (dumb-server dumb-server-buffer port)
+(defun erc-d-tests--run-proxy-direct (dumb-server dumb-server-buffer port)
   "Start DUMB-SERVER with DUMB-SERVER-BUFFER and PORT.
 These are steps shared by in-proc and subproc variants testing a
 bouncer-like setup."
@@ -1112,7 +1101,7 @@ bouncer-like setup."
                       :service port
                       :host "localhost"))
     (with-current-buffer dumb-server-buffer
-      (funcall expect 3 "Connection"))
+      (funcall expect 3 "open from"))
     (process-send-string client-foo "PASS :foo:changeme\r\n")
     (process-send-string client-bar "PASS :bar:changeme\r\n")
     (sleep-for 0.01)
@@ -1167,9 +1156,9 @@ bouncer-like setup."
          (port (process-contact dumb-server :service)))
     (with-current-buffer dumb-server-buffer
       (erc-d-t-search-for 3 "Starting"))
-    (erc-d-self--run-proxy-direct dumb-server dumb-server-buffer port)))
+    (erc-d-tests--run-proxy-direct dumb-server dumb-server-buffer port)))
 
-(cl-defun erc-d-self--start-server (&key dialogs buffer linger program libs)
+(cl-defun erc-d-tests--start-server (&key dialogs buffer linger program libs)
   "Start and return a server in a subprocess using BUFFER and PORT.
 DIALOGS are symbols representing the base names of dialog files in
 `erc-d-u-canned-dialog-dir'.  LIBS are extra files to load."
@@ -1208,24 +1197,24 @@ DIALOGS are symbols representing the base names of 
dialog files in
          (program `(setq erc-d-tmpl-vars '((fqdn . ,fqdn)
                                            (net . ,net)
                                            (network . (group (+ alpha))))))
-         (port (erc-d-self--start-server
+         (port (erc-d-tests--start-server
                 :linger 0.3
                 :program program
                 :buffer buffer
                 :dialogs '(proxy-foonet proxy-barnet)))
          (server (pop port)))
-    (erc-d-self--run-proxy-direct server buffer port)))
+    (erc-d-tests--run-proxy-direct server buffer port)))
 
 (ert-deftest erc-d-run-proxy-direct-subprocess-lib ()
   (let* ((buffer (get-buffer-create "*erc-d-server*"))
          (lib (expand-file-name "proxy-subprocess.el"
-                                erc-d-self--resources-directory))
-         (port (erc-d-self--start-server :linger 0.3
-                                         :buffer buffer
-                                         :dialogs '(proxy-foonet proxy-barnet)
-                                         :libs (list lib)))
+                                (ert-resource-directory)))
+         (port (erc-d-tests--start-server :linger 0.3
+                                          :buffer buffer
+                                          :dialogs '(proxy-foonet proxy-barnet)
+                                          :libs (list lib)))
          (server (pop port)))
-    (erc-d-self--run-proxy-direct server buffer port)))
+    (erc-d-tests--run-proxy-direct server buffer port)))
 
 (ert-deftest erc-d-run-no-pong ()
   (let* (erc-d-auto-pong
@@ -1250,7 +1239,7 @@ DIALOGS are symbols representing the base names of dialog 
files in
                        :service (process-contact dumb-server :service)
                        :host "localhost"))
     (with-current-buffer dumb-server-buffer
-      (funcall expect 3 "Connection"))
+      (funcall expect 3 "open from"))
     (process-send-string client-proc "PASS :changeme\r\nNICK tester\r\n")
     (sleep-for 0.01)
     (process-send-string client-proc "USER user 0 * :tester\r\n")
@@ -1279,7 +1268,7 @@ DIALOGS are symbols representing the base names of dialog 
files in
   (let ((erc-server-flood-penalty 0)
         (expect (erc-d-t-make-expecter))
         erc-d-linger-secs)
-    (erc-d-self-with-server (_ erc-server-buffer) incremental
+    (erc-d-tests-with-server (_ erc-server-buffer) incremental
       (with-current-buffer erc-server-buffer
         (funcall expect 3 "marked as being away"))
       (with-current-buffer erc-server-buffer
@@ -1327,4 +1316,4 @@ DIALOGS are symbols representing the base names of dialog 
files in
             (kill-buffer dumb-server-buffer)))
       (delete-file sock))))
 
-;;; erc-d-self.el ends here
+;;; erc-d-tests.el ends here
diff --git a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-u.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-u.el
index 0c834c8714..c27d67eb44 100644
--- a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-u.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-u.el
@@ -1,6 +1,6 @@
 ;;; erc-d-u.el --- Helpers for ERC test server -*- lexical-binding: t -*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d.el
index 857f6399f9..780bcecc80 100644
--- a/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d.el
@@ -1,6 +1,6 @@
 ;;; erc-d.el --- A dumb test server for ERC -*- lexical-binding: t -*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 ;;
 ;; Version: 1.1
 ;; FIXME reset^ to 1.0 or delete if adding to Emacs
@@ -318,6 +318,9 @@ PROCESS should be a client connection or a server network 
process."
        (dolist (line (split-string ,string "\r\n"))
          (erc-d--m process "<- %s:%s %s" name id line)))))
 
+(defun erc-d--log-process-event (server process msg)
+  (erc-d--m server "%s: %s" process (string-trim-right msg)))
+
 (defun erc-d--send (process string)
   "Send STRING to PROCESS peer."
   (erc-d--log process string 'outbound)
@@ -425,7 +428,7 @@ This will start the teardown for DIALOG."
 
 (defun erc-d--process-sentinel (process event)
   "Set up or tear down client-connection PROCESS depending on EVENT."
-  (erc-d--m process "Connection %s: %s" process (string-trim-right event))
+  (erc-d--log-process-event process process event)
   (if (eq 'open (process-status process))
       (erc-d--initialize-client process)
     (let* ((dialog (process-get process :dialog))
@@ -481,6 +484,7 @@ processes.  NAME is used for the process and the buffer."
                                      :buffer buf
                                      :noquery t
                                      :filter #'erc-d--filter
+                                     :log #'erc-d--log-process-event
                                      :sentinel #'erc-d--process-sentinel
                                      :name name
                                      :family (if host 'ipv4 'local)
diff --git 
a/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-subprocess.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-subprocess.el
index fed6206a6a..bb8869dff6 100644
--- a/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-subprocess.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-subprocess.el
@@ -1,6 +1,6 @@
 ;;; proxy-subprocess.el --- Example setup file for erc-d  -*- lexical-binding: 
t; -*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
diff --git a/test/lisp/erc/erc-scenarios/resources/erc-scenarios-common.el 
b/test/lisp/erc/erc-scenarios/resources/erc-scenarios-common.el
index 0237c3e862..028afa0d52 100644
--- a/test/lisp/erc/erc-scenarios/resources/erc-scenarios-common.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-scenarios-common.el
@@ -1,6 +1,6 @@
 ;;; erc-scenarios-common.el --- common helpers for ERC scenarios -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2021 Free Software Foundation, Inc.
+;; Copyright (C) 2022 Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -318,9 +318,9 @@ buffer-naming collisions involving bouncers in ERC."
            ;; Simulate disconnection and `erc-server-auto-reconnect'
            (ert-info ("Reconnect to foonet and barnet back-to-back")
              (with-current-buffer (if foo-id "oofnet" "foonet")
-               (erc-d-t-wait-for 5 (erc-server-process-alive)))
+               (erc-d-t-wait-for 10 (erc-server-process-alive)))
              (with-current-buffer (if bar-id "rabnet" "barnet")
-               (erc-d-t-wait-for 5 (erc-server-process-alive))))
+               (erc-d-t-wait-for 10 (erc-server-process-alive))))
 
            (ert-info ("#chan@foonet is exclusive to foonet")
              (with-current-buffer (if foo-id "#chan@oofnet" "#chan@foonet")
@@ -352,6 +352,62 @@ buffer-naming collisions involving bouncers in ERC."
      'stub-again 'stub-again
      'foonet-again 'barnet-again)))
 
+(defun erc-scenarios-common--upstream-reconnect (test &rest dialogs)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/upstream-reconnect")
+       (erc-d-t-cleanup-sleep-secs 1)
+       (erc-server-flood-penalty 0.1)
+       (dumb-server (apply #'erc-d-run "localhost" t dialogs))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter)))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :user "tester@vanilla/foonet"
+                                :password "changeme"
+                                :full-name "tester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)
+        (erc-d-t-wait-for 3 (eq (erc-network) 'foonet))
+        (erc-d-t-wait-for 3 (string= (buffer-name) "foonet"))
+        (funcall expect 5 "foonet")))
+
+    (ert-info ("Join #chan@foonet")
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
+        (funcall expect 5 "<alice>")))
+
+    (ert-info ("Connect to barnet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :user "tester@vanilla/barnet"
+                                :password "changeme"
+                                :full-name "tester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)
+        (erc-d-t-wait-for 6 (eq (erc-network) 'barnet))
+        (erc-d-t-wait-for 3 (string= (buffer-name) "barnet"))
+        (funcall expect 5 "barnet")))
+
+    (ert-info ("Server buffers are unique, no names based on IPs")
+      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
+
+    (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan@foonet"))
+      (funcall expect 5 "#chan was created on ")
+      (ert-info ("Joined again #chan@foonet")
+        (funcall expect 10 "#chan was created on "))
+      (while (accept-process-output erc-server-process))
+      (funcall expect 1 "My lord, in heart"))
+
+    (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan@barnet"))
+      (funcall expect 5 "#chan was created on ")
+      (ert-info ("Joined again #chan@barnet")
+        (funcall expect 10 "#chan was created on "))
+      (while (accept-process-output erc-server-process))
+      (funcall expect 10 "Go to; farewell"))
+
+    (funcall test)))
+
 ;; XXX this is okay, but we also need to check that target buffers are
 ;; already associated with a new process *before* a JOIN is sent by a
 ;; server's playback burst.  This doesn't do that.
@@ -388,7 +444,7 @@ Bug#48598: 28.0.50; buffer-naming collisions involving 
bouncers in ERC."
                                            :id foo-id))
         (setq erc-server-process-foo erc-server-process)
         (erc-scenarios-common-assert-initial-buf-name foo-id port)
-        (erc-d-t-wait-for 1 (eq (erc-network) 'foonet))
+        (erc-d-t-wait-for 5 (eq (erc-network) 'foonet))
         (funcall expect 5 "foonet")))
 
     (ert-info ("Join #chan, find sentinel, quit")
diff --git a/test/lisp/erc/erc-scenarios/resources/join/auth-source/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/join/auth-source/foonet.eld
new file mode 100644
index 0000000000..f2d7715d1e
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/resources/join/auth-source/foonet.eld
@@ -0,0 +1,33 @@
+;; -*- mode: lisp-data; -*-
+((pass 1 "PASS :changeme"))
+((nick 1 "NICK dummy"))
+((user 1 "USER user 0 * :dummy")
+ (0.00 ":irc.foonet.org 001 dummy :Welcome to the foonet IRC Network dummy")
+ (0.01 ":irc.foonet.org 002 dummy :Your host is irc.foonet.org, running 
version ergo-v2.8.0")
+ (0.00 ":irc.foonet.org 003 dummy :This server was created Tue, 24 May 2022 
05:28:42 UTC")
+ (0.00 ":irc.foonet.org 004 dummy irc.foonet.org ergo-v2.8.0 BERTZios 
CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0.00 ":irc.foonet.org 005 dummy AWAYLEN=390 BOT=B CASEMAPPING=ascii 
CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# 
ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this 
server")
+ (0.01 ":irc.foonet.org 005 dummy MAXLIST=beI:60 MAXTARGETS=4 MODES 
MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ 
TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100
 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server")
+ (0.01 ":irc.foonet.org 005 dummy draft/CHATHISTORY=100 :are supported by this 
server")
+ (0.00 ":irc.foonet.org 251 dummy :There are 0 users and 4 invisible on 1 
server(s)")
+ (0.00 ":irc.foonet.org 252 dummy 0 :IRC Operators online")
+ (0.00 ":irc.foonet.org 253 dummy 0 :unregistered connections")
+ (0.00 ":irc.foonet.org 254 dummy 2 :channels formed")
+ (0.00 ":irc.foonet.org 255 dummy :I have 4 clients and 0 servers")
+ (0.00 ":irc.foonet.org 265 dummy 4 4 :Current local users 4, max 4")
+ (0.00 ":irc.foonet.org 266 dummy 4 4 :Current global users 4, max 4")
+ (0.00 ":irc.foonet.org 422 dummy :MOTD File is missing"))
+
+((mode 1 "MODE dummy +i")
+ (0.00 ":irc.foonet.org 221 dummy +i")
+ (0.00 ":irc.foonet.org NOTICE dummy :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect.")
+ (0.02 ":irc.foonet.org 221 dummy +i"))
+
+((join 6.47 "JOIN #spam secret")
+ (0.03 ":dummy!~u@w9rfqveugz722.irc JOIN #spam"))
+
+((mode 1 "MODE #spam")
+ (0.01 ":irc.foonet.org 353 dummy = #spam :~tester dummy")
+ (0.00 ":irc.foonet.org 366 dummy #spam :End of NAMES list")
+ (0.01 ":irc.foonet.org 324 dummy #spam +knt secret")
+ (0.03 ":irc.foonet.org 329 dummy #spam 1653370308"))
diff --git a/test/lisp/erc/erc-services-tests.el 
b/test/lisp/erc/erc-services-tests.el
index 947b343278..8e2b8d2927 100644
--- a/test/lisp/erc/erc-services-tests.el
+++ b/test/lisp/erc/erc-services-tests.el
@@ -1,6 +1,6 @@
 ;;; erc-services-tests.el --- Tests for erc-services.  -*- lexical-binding:t 
-*-
 
-;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
 
 ;; This file is part of GNU Emacs.
 ;;
@@ -31,7 +31,7 @@
 
 ;;;; Core auth-source
 
-(ert-deftest erc-auth-source-determine-params-merge ()
+(ert-deftest erc--auth-source-determine-params-merge ()
   (let ((erc-session-server "irc.gnu.org")
         (erc-server-announced-name "my.gnu.org")
         (erc-session-port 6697)
@@ -39,23 +39,23 @@
         (erc-server-current-nick "tester")
         (erc-networks--id (erc-networks--id-create 'GNU.chat)))
 
-    (should (equal (erc-auth-source-determine-params-merge)
+    (should (equal (erc--auth-source-determine-params-merge)
                    '(:host ("GNU.chat" "my.gnu.org" "irc.gnu.org")
                            :port ("6697" "irc")
                            :require (:secret))))
 
-    (should (equal (erc-auth-source-determine-params-merge :host "fake")
+    (should (equal (erc--auth-source-determine-params-merge :host "fake")
                    '(:host ("fake" "GNU.chat" "my.gnu.org" "irc.gnu.org")
                            :port ("6697" "irc")
                            :require (:secret))))
 
-    (should (equal (erc-auth-source-determine-params-merge
+    (should (equal (erc--auth-source-determine-params-merge
                     :host '("fake") :require :host)
                    '(:host ("fake" "GNU.chat" "my.gnu.org" "irc.gnu.org")
                            :require (:host :secret)
                            :port ("6697" "irc"))))
 
-    (should (equal (erc-auth-source-determine-params-merge
+    (should (equal (erc--auth-source-determine-params-merge
                     :host '("fake" "GNU.chat") :port "1234" :x "x")
                    '(:host ("fake" "GNU.chat" "my.gnu.org" "irc.gnu.org")
                            :port ("1234" "6697" "irc")
@@ -65,43 +65,37 @@
 ;; Some of the following may be related to bug#23438.
 
 (defun erc-services-tests--auth-source-standard (search)
-  (let ((filter #'erc-auth-source-determine-params-merge))
-
-    (ert-info ("Session wins")
-      (let ((erc-session-server "irc.gnu.org")
-            (erc-server-announced-name "my.gnu.org")
-            (erc-session-port 6697)
-            (erc-network 'fake)
-            (erc-server-current-nick "tester")
-            (erc-networks--id (erc-networks--id-create 'GNU.chat)))
-        (should-not (funcall search))
-        (should (string= (apply search (funcall filter :user "#chan"))
-                         "foo"))))
-
-    (ert-info ("Network wins")
-      (let* ((erc-session-server "irc.gnu.org")
-             (erc-server-announced-name "my.gnu.org")
-             (erc-session-port 6697)
-             (erc-network 'GNU.chat)
-             (erc-server-current-nick "tester")
-             (erc-networks--id (erc-networks--id-create nil)))
-        (should (string= (apply search (funcall filter :user "#chan"))
-                         "foo"))))
-
-    (ert-info ("Announced wins")
-      (let ((erc-session-server "irc.gnu.org")
-            (erc-server-announced-name "my.gnu.org")
-            (erc-session-port 6697)
-            erc-network
-            (erc-networks--id (erc-networks--id-create nil)))
-        (should (string= (apply search (funcall filter :user "#chan"))
-                         "baz"))))))
+
+  (ert-info ("Session wins")
+    (let ((erc-session-server "irc.gnu.org")
+          (erc-server-announced-name "my.gnu.org")
+          (erc-session-port 6697)
+          (erc-network 'fake)
+          (erc-server-current-nick "tester")
+          (erc-networks--id (erc-networks--id-create 'GNU.chat)))
+      (should (string= (funcall search :user "#chan") "foo"))))
+
+  (ert-info ("Network wins")
+    (let* ((erc-session-server "irc.gnu.org")
+           (erc-server-announced-name "my.gnu.org")
+           (erc-session-port 6697)
+           (erc-network 'GNU.chat)
+           (erc-server-current-nick "tester")
+           (erc-networks--id (erc-networks--id-create nil)))
+      (should (string= (funcall search :user "#chan") "foo"))))
+
+  (ert-info ("Announced wins")
+    (let ((erc-session-server "irc.gnu.org")
+          (erc-server-announced-name "my.gnu.org")
+          (erc-session-port 6697)
+          erc-network
+          (erc-networks--id (erc-networks--id-create nil)))
+      (should (string= (funcall search :user "#chan") "baz")))))
 
 (defun erc-services-tests--auth-source-announced (search)
   (let* ((erc--isupport-params (make-hash-table))
          (erc-server-parameters '(("CHANTYPES" . "&#")))
-         (erc--target (erc--target-from-string "&chan"))
-         (filter #'erc-auth-source-determine-params-merge))
+         (erc--target (erc--target-from-string "&chan")))
 
     (ert-info ("Announced prioritized")
 
@@ -112,8 +106,7 @@
                (erc-network 'GNU.chat)
                (erc-server-current-nick "tester")
                (erc-networks--id (erc-networks--id-create nil)))
-          (should (string= (apply search (funcall filter :user "#chan"))
-                           "baz"))))
+          (should (string= (funcall search :user "#chan") "baz"))))
 
       (ert-info ("Peer next")
         (let* ((erc-server-announced-name "irc.gnu.org")
@@ -121,16 +114,14 @@
                (erc-network 'GNU.chat)
                (erc-server-current-nick "tester")
                (erc-networks--id (erc-networks--id-create nil)))
-          (should (string= (apply search (funcall filter :user "#chan"))
-                           "bar"))))
+          (should (string= (funcall search :user "#chan") "bar"))))
 
       (ert-info ("Network used as fallback")
         (let* ((erc-session-port 6697)
                (erc-network 'GNU.chat)
                (erc-server-current-nick "tester")
                (erc-networks--id (erc-networks--id-create nil)))
-          (should (string= (apply search (funcall filter :user "#chan"))
-                           "foo")))))))
+          (should (string= (funcall search :user "#chan") "foo")))))))
 
 (defun erc-services-tests--auth-source-overrides (search)
   (let* ((erc-session-server "irc.gnu.org")
@@ -138,27 +129,22 @@
          (erc-network 'GNU.chat)
          (erc-server-current-nick "tester")
          (erc-networks--id (erc-networks--id-create nil))
-         (erc-session-port 6667)
-         (filter #'erc-auth-source-determine-params-merge))
+         (erc-session-port 6667))
 
     (ert-info ("Specificity and overrides")
 
       (ert-info ("More specific port")
         (let ((erc-session-port 6697))
-          (should (string= (apply search (funcall filter :user "#chan"))
-                           "spam"))))
+          (should (string= (funcall search :user "#chan") "spam"))))
 
       (ert-info ("More specific user (network loses)")
-        (should (string= (apply search (funcall filter :user '("#fsf")))
-                         "42")))
+        (should (string= (funcall search :user '("#fsf")) "42")))
 
       (ert-info ("Actual override")
-        (should (string= (apply search (funcall filter :port "6667"))
-                         "sesame")))
+        (should (string= (funcall search :port "6667") "sesame")))
 
       (ert-info ("Overrides don't interfere with post-processing")
-        (should (string= (apply search (funcall filter :host "MyHost"))
-                         "123"))))))
+        (should (string= (funcall search :host "MyHost") "123"))))))
 
 ;; auth-source netrc backend
 
@@ -167,6 +153,7 @@
     "machine my.gnu.org port irc user \"#chan\" password baz"
     "machine GNU.chat port irc user \"#chan\" password foo"))
 
+;; FIXME explain what this is for
 (defun erc-services-tests--auth-source-shuffle (&rest extra)
   (string-join `(,@(sort (append erc-services-tests--auth-source-entries extra)
                          (lambda (&rest _) (zerop (random 2))))
@@ -180,8 +167,7 @@
 
     (let ((auth-sources (list netrc-file))
           (auth-source-do-cache nil))
-      (erc-services-tests--auth-source-standard
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-standard #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--netrc-announced ()
   (ert-with-temp-file netrc-file
@@ -190,8 +176,7 @@
 
     (let ((auth-sources (list netrc-file))
           (auth-source-do-cache nil))
-      (erc-services-tests--auth-source-announced
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-announced #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--netrc-overrides ()
   (ert-with-temp-file netrc-file
@@ -205,8 +190,7 @@
 
     (let ((auth-sources (list netrc-file))
           (auth-source-do-cache nil))
-      (erc-services-tests--auth-source-overrides
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-overrides #'erc-auth-source-search))))
 
 ;; auth-source plstore backend
 
@@ -218,7 +202,7 @@
               (lambda (&rest _) "" '((program . "/bin/true")))
               '((name . erc--auth-source-plstore)))
   (unwind-protect
-      (apply #'erc--auth-source-search args)
+      (apply #'erc-auth-source-search args)
     (advice-remove 'epg-decrypt-string 'erc--auth-source-plstore)
     (advice-remove 'epg-find-configuration 'erc--auth-source-plstore)))
 
@@ -323,8 +307,7 @@
              erc-services-tests--auth-source-json-standard-entries))
     (let ((auth-sources (list json-store))
           (auth-source-do-cache nil))
-      (erc-services-tests--auth-source-standard
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-standard #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--json-announced ()
   (ert-with-temp-file plstore-file
@@ -335,8 +318,7 @@
 
     (let ((auth-sources (list plstore-file))
           (auth-source-do-cache nil))
-      (erc-services-tests--auth-source-announced
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-announced #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--json-overrides ()
   (ert-with-temp-file json-file
@@ -353,8 +335,7 @@
 
     (let ((auth-sources (list json-file))
           (auth-source-do-cache nil))
-      (erc-services-tests--auth-source-overrides
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-overrides #'erc-auth-source-search))))
 
 ;; auth-source-secrets backend
 
@@ -401,8 +382,7 @@
                  (should (equal col "Test"))
                  (assoc-default label entries))))
 
-      (erc-services-tests--auth-source-standard
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-standard #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--secrets-announced ()
   (skip-unless (bound-and-true-p secrets-enabled))
@@ -425,8 +405,7 @@
                  (should (equal col "Test"))
                  (assoc-default label entries))))
 
-      (erc-services-tests--auth-source-announced
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-announced #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--secrets-overrides ()
   (skip-unless (bound-and-true-p secrets-enabled))
@@ -468,8 +447,7 @@
                  (should (equal col "Test"))
                  (assoc-default label entries))))
 
-      (erc-services-tests--auth-source-overrides
-       #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-overrides #'erc-auth-source-search))))
 
 ;; auth-source-pass backend
 
@@ -509,7 +487,7 @@
               ((symbol-function 'auth-source-pass-entries)
                (lambda () (mapcar #'car store))))
 
-      (erc-services-tests--auth-source-standard #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-standard #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--pass-announced ()
   (ert-skip "Pass backend not yet supported")
@@ -522,7 +500,7 @@
               ((symbol-function 'auth-source-pass-entries)
                (lambda () (mapcar #'car store))))
 
-      (erc-services-tests--auth-source-announced #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-announced #'erc-auth-source-search))))
 
 (ert-deftest erc--auth-source-search--pass-overrides ()
   (ert-skip "Pass backend not yet supported")
@@ -546,7 +524,7 @@
               ((symbol-function 'auth-source-pass-entries)
                (lambda () (mapcar #'car store))))
 
-      (erc-services-tests--auth-source-overrides #'erc--auth-source-search))))
+      (erc-services-tests--auth-source-overrides #'erc-auth-source-search))))
 
 ;;;; The services module
 
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index be09207b0d..c0d7c43e3b 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -136,6 +136,8 @@
     (kill-buffer "#spam")))
 
 (defun erc-tests--send-prep ()
+  ;; Caller should probably shadow `erc-insert-modify-hook' or
+  ;; populate user tables for erc-button.
   (erc-mode)
   (insert "\n\n")
   (setq erc-input-marker (make-marker)



reply via email to

[Prev in Thread] Current Thread [Next in Thread]