diff options
| author | F. Jason Park | 2023-10-06 17:34:04 -0700 |
|---|---|---|
| committer | F. Jason Park | 2023-10-13 07:47:00 -0700 |
| commit | d46c016fbd09cbce9ef23fe2b49d4fb5fc3b2b16 (patch) | |
| tree | 7f1acf7155396ca7324853051071b13447a8b6e6 | |
| parent | 9120d7a32ea4906d7c9460add31d37c3ca38931e (diff) | |
| download | emacs-d46c016fbd09cbce9ef23fe2b49d4fb5fc3b2b16.tar.gz emacs-d46c016fbd09cbce9ef23fe2b49d4fb5fc3b2b16.zip | |
Sort and dedupe when loading modules in erc-open
* doc/misc/erc.texi: Add new subheading "Module Loading" under the
"Modules" chapter.
* lisp/erc/erc.el (erc--sort-modules): New utility function to sort
and dedupe modules.
(erc-modules): In `custom-set' function, factor out collation into
separate utility `erc--sort-modules'.
(erc-update-modules): Call `erc--update-modules' with an argument, the
current value of `erc-modules'.
(erc--aberrant-modules): New variable, a list of symbols whose modules
ERC suspects of being incorrectly defined.
(erc--warn-about-aberrant-modules): New function to print an error
message and emit a warning prior to connecting when
`erc--aberrant-modules' is non-nil.
(erc--find-mode): Make heuristic more robust by always checking for a
mode activation command rather than just a state variable. This fixes
a compatibility bug, new in 5.6, affecting third-party modules that
autoload module definitions instead of their corresponding
mode-activation commands.
(erc--update-modules): Add new positional argument `modules'.
(erc--setup-buffer-hook): Add new default member,
`erc--warn-about-aberrant-modules'.
(erc-open): Pass sorted `erc-modules' to `erc--update-modules'.
* test/lisp/erc/erc-tests.el (erc--sort-modules): New test.
(erc-tests--update-modules): New fixture.
(erc--update-modules): Remove and rework as three separate tests
dedicated to specific contexts. The existing one had poor coverage
and was difficult, if not impossible, to follow.
(erc--update-modules/unknown, erc--update-modules/local,
erc--update-modules/realistic): New tests. (Bug#57955)
| -rw-r--r-- | doc/misc/erc.texi | 35 | ||||
| -rw-r--r-- | lisp/erc/erc.el | 53 | ||||
| -rw-r--r-- | test/lisp/erc/erc-tests.el | 163 |
3 files changed, 184 insertions, 67 deletions
diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index 3297d8b17f0..3bfa240cacc 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi | |||
| @@ -653,6 +653,41 @@ And unlike global toggles, none of these ever mutates | |||
| 653 | @code{erc-modules}. | 653 | @code{erc-modules}. |
| 654 | 654 | ||
| 655 | 655 | ||
| 656 | @anchor{Module Loading} | ||
| 657 | @subheading Module Loading | ||
| 658 | @cindex module loading | ||
| 659 | |||
| 660 | ERC loads internal modules in alphabetical order and third-party | ||
| 661 | modules as they appear in @code{erc-modules}. When defining your own | ||
| 662 | module, take care to ensure ERC can find it. An easy way to do that | ||
| 663 | is by mimicking the example in the doc string for | ||
| 664 | @code{define-erc-module}. For historical reasons, ERC also falls back | ||
| 665 | to @code{require}ing features. For example, if some module | ||
| 666 | @code{<mymod>} in @code{erc-modules} lacks a corresponding | ||
| 667 | @code{erc-<mymod>-mode} command, ERC will attempt to load the library | ||
| 668 | @code{erc-<mymod>} prior to connecting. If this fails, ERC signals an | ||
| 669 | error. Users wanting to define modules in an init files should | ||
| 670 | @code{(provide 'erc-<my-mod>)} somewhere to placate ERC. Dynamically | ||
| 671 | generating modules on the fly is not supported. | ||
| 672 | |||
| 673 | Sometimes, packages attempt to autoload a module's definition instead | ||
| 674 | of its minor-mode command, which breaks the link between the library | ||
| 675 | and the module. This means that enabling the mode by invoking its | ||
| 676 | command toggle isn't enough to load its defining library. Such | ||
| 677 | packages should instead only supply autoload cookies featuring an | ||
| 678 | explicit @code{autoload} form for their module's minor-mode command. | ||
| 679 | As mentioned above, packages can also usually avoid autoload cookies | ||
| 680 | entirely so long as their module's prefixed name matches that of its | ||
| 681 | defining library and the latter's provided feature. | ||
| 682 | |||
| 683 | Packages have also been seen to specify unnecessary top-level | ||
| 684 | @code{eval-after-load} forms, which end up being ineffective in most | ||
| 685 | cases. Another unfortunate practice is mutating @code{erc-modules} | ||
| 686 | itself in an autoloaded form. Doing this tricks Customize into | ||
| 687 | displaying the widget for @code{erc-modules} incorrectly, with | ||
| 688 | built-in modules moved from the predefined checklist to the | ||
| 689 | user-provided free-form area. | ||
| 690 | |||
| 656 | @c PRE5_4: Document every option of every module in its own subnode | 691 | @c PRE5_4: Document every option of every module in its own subnode |
| 657 | 692 | ||
| 658 | 693 | ||
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 16651b41eef..87abe2a133b 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el | |||
| @@ -2004,6 +2004,14 @@ buffer rather than a server buffer.") | |||
| 2004 | ;; each item is in the format '(old . new) | 2004 | ;; each item is in the format '(old . new) |
| 2005 | (delete-dups (mapcar #'erc--normalize-module-symbol mods))) | 2005 | (delete-dups (mapcar #'erc--normalize-module-symbol mods))) |
| 2006 | 2006 | ||
| 2007 | (defun erc--sort-modules (modules) | ||
| 2008 | "Return a copy of MODULES, deduped and led by sorted built-ins." | ||
| 2009 | (let (built-in third-party) | ||
| 2010 | (dolist (mod modules) | ||
| 2011 | (setq mod (erc--normalize-module-symbol mod)) | ||
| 2012 | (cl-pushnew mod (if (get mod 'erc--module) built-in third-party))) | ||
| 2013 | `(,@(sort built-in #'string-lessp) ,@(nreverse third-party)))) | ||
| 2014 | |||
| 2007 | (defcustom erc-modules '( autojoin button completion fill imenu irccontrols | 2015 | (defcustom erc-modules '( autojoin button completion fill imenu irccontrols |
| 2008 | list match menu move-to-prompt netsplit | 2016 | list match menu move-to-prompt netsplit |
| 2009 | networks noncommands readonly ring stamp track) | 2017 | networks noncommands readonly ring stamp track) |
| @@ -2039,16 +2047,10 @@ removed from the list will be disabled." | |||
| 2039 | (when (symbol-value f) | 2047 | (when (symbol-value f) |
| 2040 | (funcall f 0)) | 2048 | (funcall f 0)) |
| 2041 | (kill-local-variable f))))))))) | 2049 | (kill-local-variable f))))))))) |
| 2042 | (let (built-in third-party) | 2050 | ;; Calling `set-default-toplevel-value' complicates testing. |
| 2043 | (dolist (v val) | 2051 | (set sym (erc--sort-modules val)) |
| 2044 | (setq v (erc--normalize-module-symbol v)) | ||
| 2045 | (if (get v 'erc--module) | ||
| 2046 | (push v built-in) | ||
| 2047 | (push v third-party))) | ||
| 2048 | ;; Calling `set-default-toplevel-value' complicates testing | ||
| 2049 | (set sym (append (sort built-in #'string-lessp) | ||
| 2050 | (nreverse third-party)))) | ||
| 2051 | ;; this test is for the case where erc hasn't been loaded yet | 2052 | ;; this test is for the case where erc hasn't been loaded yet |
| 2053 | ;; FIXME explain how this ^ can occur or remove comment. | ||
| 2052 | (when (fboundp 'erc-update-modules) | 2054 | (when (fboundp 'erc-update-modules) |
| 2053 | (unless erc--inside-mode-toggle-p | 2055 | (unless erc--inside-mode-toggle-p |
| 2054 | (erc-update-modules)))) | 2056 | (erc-update-modules)))) |
| @@ -2112,15 +2114,29 @@ removed from the list will be disabled." | |||
| 2112 | (defun erc-update-modules () | 2114 | (defun erc-update-modules () |
| 2113 | "Enable minor mode for every module in `erc-modules'. | 2115 | "Enable minor mode for every module in `erc-modules'. |
| 2114 | Except ignore all local modules, which were introduced in ERC 5.5." | 2116 | Except ignore all local modules, which were introduced in ERC 5.5." |
| 2115 | (erc--update-modules) | 2117 | (erc--update-modules erc-modules) |
| 2116 | nil) | 2118 | nil) |
| 2117 | 2119 | ||
| 2120 | (defvar erc--aberrant-modules nil | ||
| 2121 | "Modules suspected of being improperly loaded.") | ||
| 2122 | |||
| 2123 | (defun erc--warn-about-aberrant-modules () | ||
| 2124 | (when (and erc--aberrant-modules (not erc--target)) | ||
| 2125 | (erc-button--display-error-notice-with-keys-and-warn | ||
| 2126 | "The following modules exhibited strange loading behavior: " | ||
| 2127 | (mapconcat (lambda (s) (format "`%s'" s)) erc--aberrant-modules ", ") | ||
| 2128 | ". Please contact ERC with \\[erc-bug] if you believe this to be untrue." | ||
| 2129 | " See Info:\"(erc) Module Loading\" for more.") | ||
| 2130 | (setq erc--aberrant-modules nil))) | ||
| 2131 | |||
| 2118 | (defun erc--find-mode (sym) | 2132 | (defun erc--find-mode (sym) |
| 2119 | (setq sym (erc--normalize-module-symbol sym)) | 2133 | (setq sym (erc--normalize-module-symbol sym)) |
| 2120 | (if-let* ((mode (intern-soft (concat "erc-" (symbol-name sym) "-mode"))) | 2134 | (if-let ((mode (intern-soft (concat "erc-" (symbol-name sym) "-mode"))) |
| 2121 | ((or (boundp mode) | 2135 | ((and (fboundp mode) |
| 2122 | (and (fboundp mode) | 2136 | (autoload-do-load (symbol-function mode) mode))) |
| 2123 | (autoload-do-load (symbol-function mode) mode))))) | 2137 | ((or (get sym 'erc--module) |
| 2138 | (symbol-file mode) | ||
| 2139 | (ignore (cl-pushnew sym erc--aberrant-modules))))) | ||
| 2124 | mode | 2140 | mode |
| 2125 | (and (require (or (get sym 'erc--feature) | 2141 | (and (require (or (get sym 'erc--feature) |
| 2126 | (intern (concat "erc-" (symbol-name sym)))) | 2142 | (intern (concat "erc-" (symbol-name sym)))) |
| @@ -2129,9 +2145,9 @@ Except ignore all local modules, which were introduced in ERC 5.5." | |||
| 2129 | (fboundp mode) | 2145 | (fboundp mode) |
| 2130 | mode))) | 2146 | mode))) |
| 2131 | 2147 | ||
| 2132 | (defun erc--update-modules () | 2148 | (defun erc--update-modules (modules) |
| 2133 | (let (local-modes) | 2149 | (let (local-modes) |
| 2134 | (dolist (module erc-modules local-modes) | 2150 | (dolist (module modules local-modes) |
| 2135 | (if-let ((mode (erc--find-mode module))) | 2151 | (if-let ((mode (erc--find-mode module))) |
| 2136 | (if (custom-variable-p mode) | 2152 | (if (custom-variable-p mode) |
| 2137 | (funcall mode 1) | 2153 | (funcall mode 1) |
| @@ -2158,7 +2174,7 @@ realizes it's missing some required module \"foo\", it can | |||
| 2158 | confidently call (erc-foo-mode 1) without having to learn | 2174 | confidently call (erc-foo-mode 1) without having to learn |
| 2159 | anything about the dependency's implementation.") | 2175 | anything about the dependency's implementation.") |
| 2160 | 2176 | ||
| 2161 | (defvar erc--setup-buffer-hook nil | 2177 | (defvar erc--setup-buffer-hook '(erc--warn-about-aberrant-modules) |
| 2162 | "Internal hook for module setup involving windows and frames.") | 2178 | "Internal hook for module setup involving windows and frames.") |
| 2163 | 2179 | ||
| 2164 | (defvar erc--display-context nil | 2180 | (defvar erc--display-context nil |
| @@ -2315,7 +2331,8 @@ Returns the buffer for the given server or channel." | |||
| 2315 | (setq old-point (point)) | 2331 | (setq old-point (point)) |
| 2316 | (setq delayed-modules | 2332 | (setq delayed-modules |
| 2317 | (erc--merge-local-modes (let ((erc--updating-modules-p t)) | 2333 | (erc--merge-local-modes (let ((erc--updating-modules-p t)) |
| 2318 | (erc--update-modules)) | 2334 | (erc--update-modules |
| 2335 | (erc--sort-modules erc-modules))) | ||
| 2319 | (or erc--server-reconnecting | 2336 | (or erc--server-reconnecting |
| 2320 | erc--target-priors))) | 2337 | erc--target-priors))) |
| 2321 | 2338 | ||
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 64b503832f3..0b88ad9cfa9 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el | |||
| @@ -2293,65 +2293,130 @@ | |||
| 2293 | (should (eq (erc--find-group 'smiley nil) 'erc)) | 2293 | (should (eq (erc--find-group 'smiley nil) 'erc)) |
| 2294 | (should (eq (erc--find-group 'unmorse nil) 'erc))) | 2294 | (should (eq (erc--find-group 'unmorse nil) 'erc))) |
| 2295 | 2295 | ||
| 2296 | (ert-deftest erc--update-modules () | 2296 | (ert-deftest erc--sort-modules () |
| 2297 | (let (calls | 2297 | (should (equal (erc--sort-modules '(networks foo fill bar fill stamp bar)) |
| 2298 | erc-modules | 2298 | ;; Third-party mods appear in original order. |
| 2299 | erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) | 2299 | '(fill networks stamp foo bar)))) |
| 2300 | |||
| 2301 | (defun erc-tests--update-modules (fn) | ||
| 2302 | (let* ((calls nil) | ||
| 2303 | (custom-modes nil) | ||
| 2304 | (on-load nil) | ||
| 2305 | |||
| 2306 | (get-calls (lambda () (prog1 (nreverse calls) (setq calls nil)))) | ||
| 2307 | |||
| 2308 | (add-onload (lambda (m k v) | ||
| 2309 | (put (intern m) 'erc--feature k) | ||
| 2310 | (push (cons k (lambda () (funcall v m))) on-load))) | ||
| 2300 | 2311 | ||
| 2301 | ;; This `lbaz' module is unknown, so ERC looks for it via the | 2312 | (mk-cmd (lambda (module) |
| 2302 | ;; symbol proerty `erc--feature' and, failing that, by | 2313 | (let ((mode (intern (format "erc-%s-mode" module)))) |
| 2303 | ;; `require'ing its "erc-" prefixed symbol. | 2314 | (fset mode (lambda (n) (push (cons mode n) calls)))))) |
| 2304 | (should-not (intern-soft "erc-lbaz-mode")) | 2315 | |
| 2316 | (mk-builtin (lambda (module-string) | ||
| 2317 | (let ((s (intern module-string))) | ||
| 2318 | (put s 'erc--module s)))) | ||
| 2319 | |||
| 2320 | (mk-global (lambda (module) | ||
| 2321 | (push (intern (format "erc-%s-mode" module)) | ||
| 2322 | custom-modes)))) | ||
| 2305 | 2323 | ||
| 2306 | (cl-letf (((symbol-function 'require) | 2324 | (cl-letf (((symbol-function 'require) |
| 2307 | (lambda (s &rest _) | 2325 | (lambda (s &rest _) |
| 2308 | (when (eq s 'erc--lbaz-feature) | 2326 | ;; Simulate library being loaded, things defined. |
| 2309 | (fset (intern "erc-lbaz-mode") ; local module | 2327 | (when-let ((h (alist-get s on-load))) (funcall h)) |
| 2310 | (lambda (n) (push (cons 'lbaz n) calls)))) | 2328 | (push (cons 'req s) calls))) |
| 2311 | (push s calls))) | 2329 | |
| 2312 | 2330 | ;; Spoof global module detection. | |
| 2313 | ;; Local modules | 2331 | ((symbol-function 'custom-variable-p) |
| 2314 | ((symbol-function 'erc-lbar-mode) | 2332 | (lambda (v) (memq v custom-modes)))) |
| 2315 | (lambda (n) (push (cons 'lbar n) calls))) | 2333 | |
| 2316 | ((get 'lbaz 'erc--feature) 'erc--lbaz-feature) | 2334 | (funcall fn get-calls add-onload mk-cmd mk-builtin mk-global)) |
| 2317 | 2335 | (should-not erc--aberrant-modules))) | |
| 2318 | ;; Global modules | 2336 | |
| 2319 | ((symbol-function 'erc-gfoo-mode) | 2337 | (ert-deftest erc--update-modules/unknown () |
| 2320 | (lambda (n) (push (cons 'gfoo n) calls))) | 2338 | (erc-tests--update-modules |
| 2321 | ((get 'erc-gfoo-mode 'standard-value) 'ignore) | 2339 | |
| 2340 | (lambda (get-calls _ mk-cmd _ mk-global) | ||
| 2341 | |||
| 2342 | (ert-info ("Baseline") | ||
| 2343 | (let* ((erc-modules '(foo)) | ||
| 2344 | (obarray (obarray-make)) | ||
| 2345 | (err (should-error (erc--update-modules erc-modules)))) | ||
| 2346 | (should (equal (cadr err) "`foo' is not a known ERC module")) | ||
| 2347 | (should (equal (funcall get-calls) | ||
| 2348 | `((req . ,(intern-soft "erc-foo"))))))) | ||
| 2349 | |||
| 2350 | ;; Module's mode command exists but lacks an associated file. | ||
| 2351 | (ert-info ("Bad autoload flagged as suspect") | ||
| 2352 | (should-not erc--aberrant-modules) | ||
| 2353 | (let* ((erc--aberrant-modules nil) | ||
| 2354 | (obarray (obarray-make)) | ||
| 2355 | (erc-modules (list (intern "foo")))) | ||
| 2356 | |||
| 2357 | ;; Create a mode activation command. | ||
| 2358 | (funcall mk-cmd "foo") | ||
| 2359 | |||
| 2360 | ;; Make the mode var global. | ||
| 2361 | (funcall mk-global "foo") | ||
| 2362 | |||
| 2363 | ;; No local modules to return. | ||
| 2364 | (should-not (erc--update-modules erc-modules)) | ||
| 2365 | (should (equal (mapcar #'prin1-to-string erc--aberrant-modules) | ||
| 2366 | '("foo"))) | ||
| 2367 | ;; ERC requires the library via prefixed module name. | ||
| 2368 | (should (equal (mapcar #'prin1-to-string (funcall get-calls)) | ||
| 2369 | `("(req . erc-foo)" "(erc-foo-mode . 1)")))))))) | ||
| 2370 | |||
| 2371 | ;; A local module (here, `lo2') lacks a mode toggle, so ERC tries to | ||
| 2372 | ;; load its defining library, first via the symbol property | ||
| 2373 | ;; `erc--feature', and then via an "erc-" prefixed symbol. | ||
| 2374 | (ert-deftest erc--update-modules/local () | ||
| 2375 | (erc-tests--update-modules | ||
| 2376 | |||
| 2377 | (lambda (get-calls add-onload mk-cmd mk-builtin mk-global) | ||
| 2378 | |||
| 2379 | (let* ((obarray (obarray-make 20)) | ||
| 2380 | (erc-modules (mapcar #'intern '("glo" "lo1" "lo2")))) | ||
| 2381 | |||
| 2382 | ;; Create a global and a local module. | ||
| 2383 | (mapc mk-cmd '("glo" "lo1")) | ||
| 2384 | (mapc mk-builtin '("glo" "lo1")) | ||
| 2385 | (funcall mk-global "glo") | ||
| 2386 | (funcall add-onload "lo2" 'explicit-feature-lib mk-cmd) | ||
| 2387 | |||
| 2388 | ;; Returns local modules. | ||
| 2389 | (should (equal (mapcar #'symbol-name (erc--update-modules erc-modules)) | ||
| 2390 | '("erc-lo2-mode" "erc-lo1-mode"))) | ||
| 2391 | |||
| 2392 | ;; Requiring `erc-lo2' defines `erc-lo2-mode'. | ||
| 2393 | (should (equal (mapcar #'prin1-to-string (funcall get-calls)) | ||
| 2394 | `("(erc-glo-mode . 1)" | ||
| 2395 | "(req . explicit-feature-lib)"))))))) | ||
| 2396 | |||
| 2397 | (ert-deftest erc--update-modules/realistic () | ||
| 2398 | (let ((calls nil) | ||
| 2399 | ;; Module `pcomplete' "resolves" to `completion'. | ||
| 2400 | (erc-modules '(pcomplete autojoin networks))) | ||
| 2401 | (cl-letf (((symbol-function 'require) | ||
| 2402 | (lambda (s &rest _) (push (cons 'req s) calls))) | ||
| 2403 | |||
| 2404 | ;; Spoof global module detection. | ||
| 2405 | ((symbol-function 'custom-variable-p) | ||
| 2406 | (lambda (v) | ||
| 2407 | (memq v '(erc-autojoin-mode erc-networks-mode | ||
| 2408 | erc-completion-mode)))) | ||
| 2409 | ;; Mock and spy real builtins. | ||
| 2322 | ((symbol-function 'erc-autojoin-mode) | 2410 | ((symbol-function 'erc-autojoin-mode) |
| 2323 | (lambda (n) (push (cons 'autojoin n) calls))) | 2411 | (lambda (n) (push (cons 'autojoin n) calls))) |
| 2324 | ((get 'erc-autojoin-mode 'standard-value) 'ignore) | ||
| 2325 | ((symbol-function 'erc-networks-mode) | 2412 | ((symbol-function 'erc-networks-mode) |
| 2326 | (lambda (n) (push (cons 'networks n) calls))) | 2413 | (lambda (n) (push (cons 'networks n) calls))) |
| 2327 | ((get 'erc-networks-mode 'standard-value) 'ignore) | ||
| 2328 | ((symbol-function 'erc-completion-mode) | 2414 | ((symbol-function 'erc-completion-mode) |
| 2329 | (lambda (n) (push (cons 'completion n) calls))) | 2415 | (lambda (n) (push (cons 'completion n) calls)))) |
| 2330 | ((get 'erc-completion-mode 'standard-value) 'ignore)) | ||
| 2331 | |||
| 2332 | (ert-info ("Unknown module") | ||
| 2333 | (setq erc-modules '(lfoo)) | ||
| 2334 | (should-error (erc--update-modules)) | ||
| 2335 | (should (equal (pop calls) 'erc-lfoo)) | ||
| 2336 | (should-not calls)) | ||
| 2337 | 2416 | ||
| 2338 | (ert-info ("Local modules") | 2417 | (should-not (erc--update-modules erc-modules)) ; no locals |
| 2339 | (setq erc-modules '(gfoo lbar lbaz)) | 2418 | (should (equal (nreverse calls) |
| 2340 | ;; Don't expose the mode here | 2419 | '((completion . 1) (autojoin . 1) (networks . 1))))))) |
| 2341 | (should (equal (mapcar #'symbol-name (erc--update-modules)) | ||
| 2342 | '("erc-lbaz-mode" "erc-lbar-mode"))) | ||
| 2343 | ;; Lbaz required because unknown. | ||
| 2344 | (should (equal (nreverse calls) '((gfoo . 1) erc--lbaz-feature))) | ||
| 2345 | (fmakunbound (intern "erc-lbaz-mode")) | ||
| 2346 | (unintern (intern "erc-lbaz-mode") obarray) | ||
| 2347 | (setq calls nil)) | ||
| 2348 | |||
| 2349 | (ert-info ("Global modules") ; `pcomplete' resolved to `completion' | ||
| 2350 | (setq erc-modules '(pcomplete autojoin networks)) | ||
| 2351 | (should-not (erc--update-modules)) ; no locals | ||
| 2352 | (should (equal (nreverse calls) | ||
| 2353 | '((completion . 1) (autojoin . 1) (networks . 1)))) | ||
| 2354 | (setq calls nil))))) | ||
| 2355 | 2420 | ||
| 2356 | (ert-deftest erc--merge-local-modes () | 2421 | (ert-deftest erc--merge-local-modes () |
| 2357 | (cl-letf (((get 'erc-b-mode 'erc-module) 'b) | 2422 | (cl-letf (((get 'erc-b-mode 'erc-module) 'b) |