aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorF. Jason Park2021-05-13 03:33:33 -0700
committerF. Jason Park2022-06-30 15:03:26 -0700
commit9be08ceb314888c7f86bddbec6490e7ead718a88 (patch)
tree9f8dacca61ee9beba3fc7a726a923e1aba282516
parente958a2b726fdcb5a4f58169e6f4f384f5786f86a (diff)
downloademacs-9be08ceb314888c7f86bddbec6490e7ead718a88.tar.gz
emacs-9be08ceb314888c7f86bddbec6490e7ead718a88.zip
Add ERC test server and related resources
* test/lisp/erc/resources/erc-d/erc-d.el: New file. * test/lisp/erc/resources/erc-d/erc-d-u.el: New file. * test/lisp/erc/resources/erc-d/erc-d-i.el: New file. * test/lisp/erc/resources/erc-d/erc-d-t.el: New file. * test/lisp/erc/resources/erc-d/erc-d-tests.el: New file. * test/lisp/erc/erc-scenarios-internal.el: New file to serve as discoverable proxy for erc-d-tests.
-rw-r--r--test/lisp/erc/erc-scenarios-internal.el27
-rw-r--r--test/lisp/erc/resources/erc-d/erc-d-i.el126
-rw-r--r--test/lisp/erc/resources/erc-d/erc-d-t.el170
-rw-r--r--test/lisp/erc/resources/erc-d/erc-d-tests.el1346
-rw-r--r--test/lisp/erc/resources/erc-d/erc-d-u.el213
-rw-r--r--test/lisp/erc/resources/erc-d/erc-d.el997
-rw-r--r--test/lisp/erc/resources/erc-d/resources/basic.eld32
-rw-r--r--test/lisp/erc/resources/erc-d/resources/depleted.eld12
-rw-r--r--test/lisp/erc/resources/erc-d/resources/drop-a.eld4
-rw-r--r--test/lisp/erc/resources/erc-d/resources/drop-b.eld4
-rw-r--r--test/lisp/erc/resources/erc-d/resources/dynamic-barnet.eld33
-rw-r--r--test/lisp/erc/resources/erc-d/resources/dynamic-foonet.eld32
-rw-r--r--test/lisp/erc/resources/erc-d/resources/dynamic-stub.eld4
-rw-r--r--test/lisp/erc/resources/erc-d/resources/dynamic.eld30
-rw-r--r--test/lisp/erc/resources/erc-d/resources/eof.eld33
-rw-r--r--test/lisp/erc/resources/erc-d/resources/fuzzy.eld42
-rw-r--r--test/lisp/erc/resources/erc-d/resources/incremental.eld43
-rw-r--r--test/lisp/erc/resources/erc-d/resources/irc-parser-tests.eld380
-rw-r--r--test/lisp/erc/resources/erc-d/resources/linger-multi-a.eld3
-rw-r--r--test/lisp/erc/resources/erc-d/resources/linger-multi-b.eld3
-rw-r--r--test/lisp/erc/resources/erc-d/resources/linger.eld33
-rw-r--r--test/lisp/erc/resources/erc-d/resources/no-block.eld55
-rw-r--r--test/lisp/erc/resources/erc-d/resources/no-match.eld32
-rw-r--r--test/lisp/erc/resources/erc-d/resources/no-pong.eld27
-rw-r--r--test/lisp/erc/resources/erc-d/resources/nonstandard.eld6
-rw-r--r--test/lisp/erc/resources/erc-d/resources/proxy-barnet.eld24
-rw-r--r--test/lisp/erc/resources/erc-d/resources/proxy-foonet.eld24
-rw-r--r--test/lisp/erc/resources/erc-d/resources/proxy-solo.eld9
-rw-r--r--test/lisp/erc/resources/erc-d/resources/proxy-subprocess.el45
-rw-r--r--test/lisp/erc/resources/erc-d/resources/timeout.eld27
-rw-r--r--test/lisp/erc/resources/erc-d/resources/unexpected.eld28
31 files changed, 3844 insertions, 0 deletions
diff --git a/test/lisp/erc/erc-scenarios-internal.el b/test/lisp/erc/erc-scenarios-internal.el
new file mode 100644
index 00000000000..e4e1edb97e3
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios-internal.el
@@ -0,0 +1,27 @@
1;;; erc-scenarios-internal.el --- Proxy file for erc-d tests -*- lexical-binding: t -*-
2
3;; Copyright (C) 2022 Free Software Foundation, Inc.
4;;
5;; This file is part of GNU Emacs.
6;;
7;; This program is free software: you can redistribute it and/or
8;; modify it under the terms of the GNU General Public License as
9;; published by the Free Software Foundation, either version 3 of the
10;; License, or (at your option) any later version.
11;;
12;; This program is distributed in the hope that it will be useful, but
13;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15;; General Public License for more details.
16;;
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see
19;; <https://www.gnu.org/licenses/>.
20
21(require 'ert-x)
22(eval-and-compile
23 (let ((load-path (cons (expand-file-name "erc-d" (ert-resource-directory))
24 load-path)))
25 (load "erc-d-tests" nil 'silent)))
26
27;;; erc-scenarios-internal.el ends here
diff --git a/test/lisp/erc/resources/erc-d/erc-d-i.el b/test/lisp/erc/resources/erc-d/erc-d-i.el
new file mode 100644
index 00000000000..27b1bf60839
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/erc-d-i.el
@@ -0,0 +1,126 @@
1;;; erc-d-i.el --- IRC helpers for ERC test server -*- lexical-binding: t -*-
2
3;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
4;;
5;; This file is part of GNU Emacs.
6;;
7;; This program is free software: you can redistribute it and/or
8;; modify it under the terms of the GNU General Public License as
9;; published by the Free Software Foundation, either version 3 of the
10;; License, or (at your option) any later version.
11;;
12;; This program is distributed in the hope that it will be useful, but
13;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15;; General Public License for more details.
16;;
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see
19;; <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;;; Code:
24
25(require 'cl-lib)
26
27(cl-defstruct (erc-d-i-message (:conc-name erc-d-i-message.))
28 "Identical to `erc-response'.
29When member `compat' is nil, it means the raw message was decoded as
30UTF-8 text before parsing, which is nonstandard."
31 (unparsed "" :type string)
32 (sender "" :type string)
33 (command "" :type string)
34 (command-args nil :type (list-of string))
35 (contents "" :type string)
36 (tags nil :type (list-of (cons symbol string)))
37 (compat t :type boolean))
38
39(defconst erc-d-i--tag-escapes
40 '((";" . "\\:") (" " . "\\s") ("\\" . "\\\\") ("\r" . "\\r") ("\n" . "\\n")))
41
42;; XXX these are not mirror inverses; unescaping may degenerate
43;; original by dropping stranded/misplaced backslashes.
44
45(defconst erc-d-i--tag-escaped-regexp (rx (or ?\; ?\ ?\\ ?\r ?\n)))
46
47(defconst erc-d-i--tag-unescaped-regexp
48 (rx (or "\\:" "\\s" "\\\\" "\\r" "\\n"
49 (seq "\\" (or string-end (not (or ":" "n" "r" "\\")))))))
50
51(defun erc-d-i--unescape-tag-value (str)
52 "Undo substitution of char placeholders in raw tag value STR."
53 (replace-regexp-in-string erc-d-i--tag-unescaped-regexp
54 (lambda (s)
55 (or (car (rassoc s erc-d-i--tag-escapes))
56 (substring s 1)))
57 str t t))
58
59(defun erc-d-i--escape-tag-value (str)
60 "Swap out banned chars in tag value STR with message representation."
61 (replace-regexp-in-string erc-d-i--tag-escaped-regexp
62 (lambda (s)
63 (cdr (assoc s erc-d-i--tag-escapes)))
64 str t t))
65
66(defconst erc-d-i--invalid-tag-regexp (rx (any "\0\7\r\n; ")))
67
68;; This is `erc-v3-message-tags' with fatal errors.
69
70(defun erc-d-i--validate-tags (raw)
71 "Validate tags portion of some RAW incoming message.
72RAW must not have a leading \"@\" or a trailing space. The spec says
73validation shouldn't be performed on keys and that undecodeable values
74or ones with illegal (unescaped) chars may be dropped. This does not
75respect any of that. Its purpose is to catch bad input created by us."
76 (unless (> 4094 (string-bytes raw))
77 ;; 417 ERR_INPUTTOOLONG Input line was too long
78 (error "Message tags exceed 4094 bytes: %S" raw))
79 (let (tags
80 (tag-strings (split-string raw ";")))
81 (dolist (s tag-strings (nreverse tags))
82 (let* ((m (if (>= emacs-major-version 28)
83 (string-search "=" s)
84 (string-match-p "=" s)))
85 (key (if m (substring s 0 m) s))
86 (val (when-let* (m ; check first, like (m), but shadow
87 (v (substring s (1+ m)))
88 ((not (string-equal v ""))))
89 (when (string-match-p erc-d-i--invalid-tag-regexp v)
90 (error "Bad tag: %s" s))
91 (thread-first v
92 (decode-coding-string 'utf-8 t)
93 (erc-d-i--unescape-tag-value)))))
94 (when (string-empty-p key)
95 (error "Tag missing key: %S" s))
96 (setf (alist-get (intern key) tags) val)))))
97
98(defun erc-d-i--parse-message (s &optional decode)
99 "Parse string S into `erc-d-i-message' object.
100With DECODE, decode as UTF-8 text."
101 (when (string-suffix-p "\r\n" s)
102 (error "Unstripped message encountered"))
103 (when decode
104 (setq s (decode-coding-string s 'utf-8 t)))
105 (let ((mes (make-erc-d-i-message :unparsed s :compat (not decode)))
106 tokens)
107 (when-let* (((not (string-empty-p s)))
108 ((eq ?@ (aref s 0)))
109 (m (string-match " " s))
110 (u (substring s 1 m)))
111 (setf (erc-d-i-message.tags mes) (erc-d-i--validate-tags u)
112 s (substring s (1+ m))))
113 (if-let* ((m (string-match " :" s))
114 (other-toks (split-string (substring s 0 m) " " t))
115 (rest (substring s (+ 2 m))))
116 (setf (erc-d-i-message.contents mes) rest
117 tokens (nconc other-toks (list rest)))
118 (setq tokens (split-string s " " t " ")))
119 (when (and tokens (eq ?: (aref (car tokens) 0)))
120 (setf (erc-d-i-message.sender mes) (substring (pop tokens) 1)))
121 (setf (erc-d-i-message.command mes) (or (pop tokens) "")
122 (erc-d-i-message.command-args mes) tokens)
123 mes))
124
125(provide 'erc-d-i)
126;;; erc-d-i.el ends here
diff --git a/test/lisp/erc/resources/erc-d/erc-d-t.el b/test/lisp/erc/resources/erc-d/erc-d-t.el
new file mode 100644
index 00000000000..a1a7e7e88d5
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/erc-d-t.el
@@ -0,0 +1,170 @@
1;;; erc-d-t.el --- ERT helpers for ERC test server -*- lexical-binding: t -*-
2
3;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
4;;
5;; This file is part of GNU Emacs.
6;;
7;; This program is free software: you can redistribute it and/or
8;; modify it under the terms of the GNU General Public License as
9;; published by the Free Software Foundation, either version 3 of the
10;; License, or (at your option) any later version.
11;;
12;; This program is distributed in the hope that it will be useful, but
13;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15;; General Public License for more details.
16;;
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see
19;; <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;;; Code:
24(eval-and-compile
25 (let* ((d (file-name-directory (or (macroexp-file-name) buffer-file-name)))
26 (load-path (cons (directory-file-name d) load-path)))
27 (require 'erc-d-u)))
28
29(require 'ert)
30
31(defun erc-d-t-kill-related-buffers ()
32 "Kill all erc- or erc-d- related buffers."
33 (let (buflist)
34 (dolist (buf (buffer-list))
35 (with-current-buffer buf
36 (when (or erc-d-u--process-buffer
37 (derived-mode-p 'erc-mode))
38 (push buf buflist))))
39 (dolist (buf buflist)
40 (when (and (boundp 'erc-server-flood-timer)
41 (timerp erc-server-flood-timer))
42 (cancel-timer erc-server-flood-timer))
43 (when-let ((proc (get-buffer-process buf)))
44 (delete-process proc))
45 (when (buffer-live-p buf)
46 (kill-buffer buf))))
47 (while (when-let ((buf (pop erc-d-u--canned-buffers)))
48 (kill-buffer buf))))
49
50(defun erc-d-t-silence-around (orig &rest args)
51 "Run ORIG function with ARGS silently.
52Use this on `erc-handle-login' and `erc-server-connect'."
53 (let ((inhibit-message t))
54 (apply orig args)))
55
56(defvar erc-d-t-cleanup-sleep-secs 0.1)
57
58(defmacro erc-d-t-with-cleanup (bindings cleanup &rest body)
59 "Execute BODY and run CLEANUP form regardless of outcome.
60`let*'-bind BINDINGS and make them available in BODY and CLEANUP.
61After CLEANUP, destroy any values in BINDINGS that remain bound to
62buffers or processes. Sleep `erc-d-t-cleanup-sleep-secs' before
63returning."
64 (declare (indent 2))
65 `(let* ,bindings
66 (unwind-protect
67 (progn ,@body)
68 ,cleanup
69 (when noninteractive
70 (let (bufs procs)
71 (dolist (o (list ,@(mapcar (lambda (b) (or (car-safe b) b))
72 bindings)))
73 (when (bufferp o)
74 (push o bufs))
75 (when (processp o)
76 (push o procs)))
77 (dolist (proc procs)
78 (delete-process proc)
79 (when-let ((buf (process-buffer proc)))
80 (push buf bufs)))
81 (dolist (buf bufs)
82 (when-let ((proc (get-buffer-process buf)))
83 (delete-process proc))
84 (when (bufferp buf)
85 (ignore-errors (kill-buffer buf)))))
86 (sleep-for erc-d-t-cleanup-sleep-secs)))))
87
88(defmacro erc-d-t-wait-for (max-secs msg &rest body)
89 "Wait for BODY to become non-nil.
90Or signal error with MSG after MAX-SECS. When MAX-SECS is negative,
91signal if BODY is ever non-nil before MAX-SECS elapses. On success,
92return BODY's value.
93
94Note: this assumes BODY is waiting on a peer's output. It tends to
95artificially accelerate consumption of all process output, which may not
96be desirable."
97 (declare (indent 2))
98 (unless (or (stringp msg) (memq (car-safe msg) '(format concat)))
99 (push msg body)
100 (setq msg (prin1-to-string body)))
101 (let ((inverted (make-symbol "inverted"))
102 (time-out (make-symbol "time-out"))
103 (result (make-symbol "result")))
104 `(ert-info ((concat "Awaiting: " ,msg))
105 (let ((,time-out (abs ,max-secs))
106 (,inverted (< ,max-secs 0))
107 (,result ',result))
108 (with-timeout (,time-out (if ,inverted
109 (setq ,inverted nil)
110 (error "Failed awaiting: %s" ,msg)))
111 (while (not (setq ,result (progn ,@body)))
112 (when (and (accept-process-output nil 0.1) (not noninteractive))
113 (redisplay))))
114 (when ,inverted
115 (error "Failed awaiting: %s" ,msg))
116 ,result))))
117
118(defmacro erc-d-t-ensure-for (max-secs msg &rest body)
119 "Ensure BODY remains non-nil for MAX-SECS.
120On failure, emit MSG."
121 (declare (indent 2))
122 (unless (or (stringp msg) (memq (car-safe msg) '(format concat)))
123 (push msg body)
124 (setq msg (prin1-to-string body)))
125 `(erc-d-t-wait-for (- (abs ,max-secs)) ,msg (not (progn ,@body))))
126
127(defun erc-d-t-search-for (timeout text &optional from on-success)
128 "Wait for TEXT to appear in current buffer before TIMEOUT secs.
129With marker or number FROM, only consider the portion of the buffer from
130that point forward. If TEXT is a cons, interpret it as an RX regular
131expression. If ON-SUCCESS is a function, call it when TEXT is found."
132 (save-restriction
133 (widen)
134 (let* ((rxp (consp text))
135 (fun (if rxp #'search-forward-regexp #'search-forward))
136 (pat (if rxp (rx-to-string text) text))
137 res)
138 (erc-d-t-wait-for timeout (format "string: %s" text)
139 (goto-char (or from (point-min)))
140 (setq res (funcall fun pat nil t))
141 (if (and on-success res)
142 (funcall on-success)
143 res)))))
144
145(defun erc-d-t-absent-for (timeout text &optional from on-success)
146 "Assert TEXT doesn't appear in current buffer for TIMEOUT secs."
147 (erc-d-t-search-for (- (abs timeout)) text from on-success))
148
149(defun erc-d-t-make-expecter ()
150 "Return function to search for new output in buffer.
151Assume new text is only inserted at or after `erc-insert-marker'.
152
153The returned function works like `erc-d-t-search-for', but it never
154revisits previously covered territory, and the optional fourth argument,
155ON-SUCCESS, is nonexistent. To reset, specify a FROM argument."
156 (let (positions)
157 (lambda (timeout text &optional reset-from)
158 (let* ((pos (cdr (assq (current-buffer) positions)))
159 (cb (lambda ()
160 (unless pos
161 (push (cons (current-buffer) (setq pos (make-marker)))
162 positions))
163 (marker-position
164 (set-marker pos (min (point) (1- (point-max))))))))
165 (when reset-from
166 (set-marker pos reset-from))
167 (erc-d-t-search-for timeout text pos cb)))))
168
169(provide 'erc-d-t)
170;;; erc-d-t.el ends here
diff --git a/test/lisp/erc/resources/erc-d/erc-d-tests.el b/test/lisp/erc/resources/erc-d/erc-d-tests.el
new file mode 100644
index 00000000000..f64b5e8a74c
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/erc-d-tests.el
@@ -0,0 +1,1346 @@
1;;; erc-d-tests.el --- tests for erc-d -*- lexical-binding: t -*-
2
3;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
4;;
5;; This file is part of GNU Emacs.
6;;
7;; This program is free software: you can redistribute it and/or
8;; modify it under the terms of the GNU General Public License as
9;; published by the Free Software Foundation, either version 3 of the
10;; License, or (at your option) any later version.
11;;
12;; This program is distributed in the hope that it will be useful, but
13;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15;; General Public License for more details.
16;;
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see
19;; <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;;; Code:
24(require 'ert-x)
25(eval-and-compile
26 (let ((load-path (cons (expand-file-name ".." (ert-resource-directory))
27 load-path)))
28 (require 'erc-d)
29 (require 'erc-d-t)))
30
31(require 'erc)
32
33;; Temporary kludge to silence warning
34(put 'erc-parse-tags 'erc-v3-warned-p t)
35
36(ert-deftest erc-d-u--canned-load-dialog--basic ()
37 (should-not (get-buffer "basic.eld"))
38 (should-not erc-d-u--canned-buffers)
39 (let* ((exes (erc-d-u--canned-load-dialog 'basic))
40 (reap (lambda ()
41 (cl-loop with e = (erc-d-u--read-dialog exes)
42 for s = (erc-d-u--read-exchange e)
43 while s collect s))))
44 (should (get-buffer "basic.eld"))
45 (should (memq (get-buffer "basic.eld") erc-d-u--canned-buffers))
46 (should (equal (funcall reap) '((pass 10.0 "PASS " (? ?:) "changeme"))))
47 (should (equal (funcall reap) '((nick 0.2 "NICK tester"))))
48 (let ((r (funcall reap)))
49 (should (equal (car r) '(user 0.2 "USER user 0 * :tester")))
50 (should (equal
51 (car (last r))
52 '(0 ":irc.example.org 422 tester :MOTD File is missing"))))
53 (should (equal (car (funcall reap)) '(mode-user 5 "MODE tester +i")))
54 (should (equal (funcall reap)
55 '((mode-chan 1.2 "MODE #chan")
56 (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey"))))
57 ;; See `define-error' site for `iter-end-of-sequence'
58 (ert-info ("EOB detected") (should-not (erc-d-u--read-dialog exes))))
59 (should-not (get-buffer "basic.eld"))
60 (should-not erc-d-u--canned-buffers))
61
62(defun erc-d-tests--make-hunk-reader (hunks)
63 (let ((p (erc-d-u--read-dialog hunks)))
64 (lambda () (erc-d-u--read-exchange p))))
65
66;; Fuzzies need to be able to access any non-exhausted genny.
67(ert-deftest erc-d-u--canned-load-dialog--intermingled ()
68 (should-not (get-buffer "basic.eld"))
69 (should-not erc-d-u--canned-buffers)
70 (let* ((exes (erc-d-u--canned-load-dialog 'basic))
71 (pass (erc-d-tests--make-hunk-reader exes))
72 (nick (erc-d-tests--make-hunk-reader exes))
73 (user (erc-d-tests--make-hunk-reader exes))
74 (modu (erc-d-tests--make-hunk-reader exes))
75 (modc (erc-d-tests--make-hunk-reader exes)))
76
77 (should (equal (funcall user) '(user 0.2 "USER user 0 * :tester")))
78 (should (equal (funcall modu) '(mode-user 5 "MODE tester +i")))
79 (should (equal (funcall modc) '(mode-chan 1.2 "MODE #chan")))
80
81 (cl-loop repeat 8 do (funcall user)) ; skip a few
82 (should (equal (funcall user)
83 '(0 ":irc.example.org 254 tester 1 :channels formed")))
84 (should (equal (funcall modu)
85 '(0 ":irc.example.org 221 tester +Zi")))
86 (should (equal (cl-loop for s = (funcall modc) while s collect s) ; done
87 '((0.1 ":bob!~bob@example.org PRIVMSG #chan :hey"))))
88
89 (cl-loop repeat 3 do (funcall user))
90 (cl-loop repeat 3 do (funcall modu))
91
92 (ert-info ("Change up the order")
93 (should
94 (equal (funcall modu)
95 '(0 ":irc.example.org 366 alice #chan :End of NAMES list")))
96 (should
97 (equal (funcall user)
98 '(0 ":irc.example.org 422 tester :MOTD File is missing"))))
99
100 ;; Exhaust these
101 (should (equal (cl-loop for s = (funcall pass) while s collect s) ; done
102 '((pass 10.0 "PASS " (? ?:) "changeme"))))
103 (should (equal (cl-loop for s = (funcall nick) while s collect s) ; done
104 '((nick 0.2 "NICK tester"))))
105
106 (ert-info ("End of file but no teardown because hunks outstanding")
107 (should-not (erc-d-u--read-dialog exes))
108 (should (get-buffer "basic.eld")))
109
110 ;; Finish
111 (should-not (funcall user))
112 (should-not (funcall modu)))
113
114 (should-not (get-buffer "basic.eld"))
115 (should-not erc-d-u--canned-buffers))
116
117;; This indirectly tests `erc-d-u--canned-read' cleanup/teardown
118
119(ert-deftest erc-d-u--rewrite-for-slow-mo ()
120 (should-not (get-buffer "basic.eld"))
121 (should-not (get-buffer "basic.eld<2>"))
122 (should-not (get-buffer "basic.eld<3>"))
123 (should-not erc-d-u--canned-buffers)
124 (let ((exes (erc-d-u--canned-load-dialog 'basic))
125 (exes-lower (erc-d-u--canned-load-dialog 'basic))
126 (exes-custom (erc-d-u--canned-load-dialog 'basic))
127 (reap (lambda (e) (cl-loop with p = (erc-d-u--read-dialog e)
128 for s = (erc-d-u--read-exchange p)
129 while s collect s))))
130 (should (get-buffer "basic.eld"))
131 (should (get-buffer "basic.eld<2>"))
132 (should (get-buffer "basic.eld<3>"))
133 (should (equal (list (get-buffer "basic.eld<3>")
134 (get-buffer "basic.eld<2>")
135 (get-buffer "basic.eld"))
136 erc-d-u--canned-buffers))
137
138 (ert-info ("Rewrite for slowmo basic")
139 (setq exes (erc-d-u--rewrite-for-slow-mo 10 exes))
140 (should (equal (funcall reap exes)
141 '((pass 20.0 "PASS " (? ?:) "changeme"))))
142 (should (equal (funcall reap exes)
143 '((nick 10.2 "NICK tester"))))
144 (let ((r (funcall reap exes)))
145 (should (equal (car r) '(user 10.2 "USER user 0 * :tester")))
146 (should (equal
147 (car (last r))
148 '(0 ":irc.example.org 422 tester :MOTD File is missing"))))
149 (should (equal (car (funcall reap exes))
150 '(mode-user 15 "MODE tester +i")))
151 (should (equal (car (funcall reap exes))
152 '(mode-chan 11.2 "MODE #chan")))
153 (should-not (erc-d-u--read-dialog exes)))
154
155 (ert-info ("Rewrite for slowmo bounded")
156 (setq exes-lower (erc-d-u--rewrite-for-slow-mo -5 exes-lower))
157 (should (equal (funcall reap exes-lower)
158 '((pass 10.0 "PASS " (? ?:) "changeme"))))
159 (should (equal (funcall reap exes-lower)
160 '((nick 5 "NICK tester"))))
161 (should (equal (car (funcall reap exes-lower))
162 '(user 5 "USER user 0 * :tester")))
163 (should (equal (car (funcall reap exes-lower))
164 '(mode-user 5 "MODE tester +i")))
165 (should (equal (car (funcall reap exes-lower))
166 '(mode-chan 5 "MODE #chan")))
167 (should-not (erc-d-u--read-dialog exes-lower)))
168
169 (ert-info ("Rewrite for slowmo custom")
170 (setq exes-custom (erc-d-u--rewrite-for-slow-mo
171 (lambda (n) (* 2 n)) exes-custom))
172 (should (equal (funcall reap exes-custom)
173 '((pass 20.0 "PASS " (? ?:) "changeme"))))
174 (should (equal (funcall reap exes-custom)
175 '((nick 0.4 "NICK tester"))))
176 (should (equal (car (funcall reap exes-custom))
177 '(user 0.4 "USER user 0 * :tester")))
178 (should (equal (car (funcall reap exes-custom))
179 '(mode-user 10 "MODE tester +i")))
180 (should (equal (car (funcall reap exes-custom))
181 '(mode-chan 2.4 "MODE #chan")))
182 (should-not (erc-d-u--read-dialog exes-custom))))
183
184 (should-not (get-buffer "basic.eld"))
185 (should-not (get-buffer "basic.eld<2>"))
186 (should-not (get-buffer "basic.eld<3>"))
187 (should-not erc-d-u--canned-buffers))
188
189(ert-deftest erc-d--active-ex-p ()
190 (let ((ring (make-ring 5)))
191 (ert-info ("Empty ring returns nil for not active")
192 (should-not (erc-d--active-ex-p ring)))
193 (ert-info ("One fuzzy member returns nil for not active")
194 (ring-insert ring (make-erc-d-exchange :tag '~foo))
195 (should-not (erc-d--active-ex-p ring)))
196 (ert-info ("One active member returns t for active")
197 (ring-insert-at-beginning ring (make-erc-d-exchange :tag 'bar))
198 (should (erc-d--active-ex-p ring)))))
199
200(defun erc-d-tests--parse-message-upstream (raw)
201 "Hack shim for parsing RAW line recvd from peer."
202 (cl-letf (((symbol-function #'erc-handle-parsed-server-response)
203 (lambda (_ p) p)))
204 (let ((erc-active-buffer nil))
205 (erc-parse-server-response nil raw))))
206
207(ert-deftest erc-d-i--validate-tags ()
208 (should (erc-d-i--validate-tags
209 (concat "batch=4cc99692bf24a4bec4aa03da437364f5;"
210 "time=2021-01-04T00:32:13.839Z")))
211 (should (erc-d-i--validate-tags "+foo=bar;baz=spam"))
212 (should (erc-d-i--validate-tags "foo=\\:ok;baz=\\s"))
213 (should (erc-d-i--validate-tags "foo=\303\247edilla"))
214 (should (erc-d-i--validate-tags "foo=\\"))
215 (should (erc-d-i--validate-tags "foo=bar\\baz"))
216 (should-error (erc-d-i--validate-tags "foo=\\\\;baz=\\\r\\\n"))
217 (should-error (erc-d-i--validate-tags "foo=\n"))
218 (should-error (erc-d-i--validate-tags "foo=\0ok"))
219 (should-error (erc-d-i--validate-tags "foo=bar baz"))
220 (should-error (erc-d-i--validate-tags "foo=bar\r"))
221 (should-error (erc-d-i--validate-tags "foo=bar;")))
222
223(ert-deftest erc-d-i--parse-message ()
224 (let* ((raw (concat "@time=2020-11-23T09:10:33.088Z "
225 ":tilde.chat BATCH +1 chathistory :#meta"))
226 (upstream (erc-d-tests--parse-message-upstream raw))
227 (ours (erc-d-i--parse-message raw)))
228
229 (ert-info ("Baseline upstream")
230 (should (equal (erc-response.unparsed upstream) raw))
231 (should (equal (erc-response.sender upstream) "tilde.chat"))
232 (should (equal (erc-response.command upstream) "BATCH"))
233 (should (equal (erc-response.command-args upstream)
234 '("+1" "chathistory" "#meta")))
235 (should (equal (erc-response.contents upstream) "#meta")))
236
237 (ert-info ("Ours my not compare cl-equalp but is otherwise the same")
238 (should (equal (erc-d-i-message.unparsed ours) raw))
239 (should (equal (erc-d-i-message.sender ours) "tilde.chat"))
240 (should (equal (erc-d-i-message.command ours) "BATCH"))
241 (should (equal (erc-d-i-message.command-args ours)
242 '("+1" "chathistory" "#meta")))
243 (should (equal (erc-d-i-message.contents ours) "#meta"))
244 (should (equal (erc-d-i-message.tags ours)
245 '((time . "2020-11-23T09:10:33.088Z")))))
246
247 (ert-info ("No compat decodes the whole message as utf-8")
248 (setq ours (erc-d-i--parse-message
249 "@foo=\303\247edilla TAGMSG #ch\303\240n"
250 'decode))
251 (should-not (erc-d-i-message.compat ours))
252 (should (equal (erc-d-i-message.command-args ours) '("#chàn")))
253 (should (equal (erc-d-i-message.contents ours) ""))
254 (should (equal (erc-d-i-message.tags ours) '((foo . "çedilla")))))))
255
256(ert-deftest erc-d-i--unescape-tag-value ()
257 (should (equal (erc-d-i--unescape-tag-value
258 "\\sabc\\sdef\\s\\sxyz\\s")
259 " abc def xyz "))
260 (should (equal (erc-d-i--unescape-tag-value
261 "\\\\abc\\\\def\\\\\\\\xyz\\\\")
262 "\\abc\\def\\\\xyz\\"))
263 (should (equal (erc-d-i--unescape-tag-value "a\\bc") "abc"))
264 (should (equal (erc-d-i--unescape-tag-value
265 "\\\\abc\\\\def\\\\\\\\xyz\\")
266 "\\abc\\def\\\\xyz"))
267 (should (equal (erc-d-i--unescape-tag-value "a\\:b\\r\\nc\\sd")
268 "a;b\r\nc d")))
269
270(ert-deftest erc-d-i--escape-tag-value ()
271 (should (equal (erc-d-i--escape-tag-value " abc def xyz ")
272 "\\sabc\\sdef\\s\\sxyz\\s"))
273 (should (equal (erc-d-i--escape-tag-value "\\abc\\def\\\\xyz\\")
274 "\\\\abc\\\\def\\\\\\\\xyz\\\\"))
275 (should (equal (erc-d-i--escape-tag-value "a;b\r\nc d")
276 "a\\:b\\r\\nc\\sd")))
277
278;; TODO add tests for msg-join, mask-match, userhost-split,
279;; validate-hostname
280
281(ert-deftest erc-d-i--parse-message--irc-parser-tests ()
282 (let* ((data (with-temp-buffer
283 (insert-file-contents
284 (expand-file-name "irc-parser-tests.eld"
285 (ert-resource-directory)))
286 (read (current-buffer))))
287 (tests (assoc-default 'tests (assoc-default 'msg-split data)))
288 input atoms m ours)
289 (dolist (test tests)
290 (setq input (assoc-default 'input test)
291 atoms (assoc-default 'atoms test)
292 m (erc-d-i--parse-message input))
293 (ert-info ("Parses tags correctly")
294 (setq ours (erc-d-i-message.tags m))
295 (if-let ((tags (assoc-default 'tags atoms)))
296 (pcase-dolist (`(,key . ,value) ours)
297 (should (string= (cdr (assq key tags)) (or value ""))))
298 (should-not ours)))
299 (ert-info ("Parses verbs correctly")
300 (setq ours (erc-d-i-message.command m))
301 (if-let ((verbs (assoc-default 'verb atoms)))
302 (should (string= (downcase verbs) (downcase ours)))
303 (should (string-empty-p ours))))
304 (ert-info ("Parses sources correctly")
305 (setq ours (erc-d-i-message.sender m))
306 (if-let ((source (assoc-default 'source atoms)))
307 (should (string= source ours))
308 (should (string-empty-p ours))))
309 (ert-info ("Parses params correctly")
310 (setq ours (erc-d-i-message.command-args m))
311 (if-let ((params (assoc-default 'params atoms)))
312 (should (equal ours params))
313 (should-not ours))))))
314
315(defun erc-d-tests--new-ex (existing raw-hunk)
316 (let* ((f (lambda (_) (pop raw-hunk)))
317 (sd (make-erc-d-u-scan-d :f f)))
318 (setf (erc-d-exchange-hunk existing) (make-erc-d-u-scan-e :sd sd)
319 (erc-d-exchange-spec existing) (make-erc-d-spec)))
320 (erc-d--iter existing))
321
322(ert-deftest erc-d--render-entries ()
323 (let* ((erc-nick "foo")
324 (dialog (make-erc-d-dialog :vars `((:a . 1)
325 (c . ((a b) (: a space b)))
326 (d . (c alpha digit))
327 (bee . 2)
328 (f . ,(lambda () "3"))
329 (i . erc-nick))))
330 (exchange (make-erc-d-exchange :dialog dialog))
331 (mex (apply-partially #'erc-d-tests--new-ex exchange))
332 it)
333
334 (erc-d-exchange-reload dialog exchange)
335
336 (ert-info ("Baseline Outgoing")
337 (setq it (funcall mex '((0 "abc"))))
338 (should (equal (funcall it) 0))
339 (should (equal (funcall it) "abc")))
340
341 (ert-info ("Incoming are regexp escaped")
342 (setq it (funcall mex '((i 0.0 "fsf" ".org"))))
343 (should (equal (cons (funcall it) (funcall it)) '(i . 0.0)))
344 (should (equal (funcall it) "\\`fsf\\.org")))
345
346 (ert-info ("Incoming can access vars via rx-let")
347 (setq it (funcall mex '((i 0.0 bee))))
348 (should (equal (cons (funcall it) (funcall it)) '(i . 0.0)))
349 (should (equal (funcall it) "\\`\002")))
350
351 (ert-info ("Incoming rx-let params")
352 (setq it (funcall mex '((i 0.0 d))))
353 (should (equal (cons (funcall it) (funcall it)) '(i . 0.0)))
354 (should (equal (funcall it) "\\`[[:alpha:]][[:space:]][[:digit:]]")))
355
356 (ert-info ("Incoming literal rx forms")
357 (setq it (funcall mex '((i 0.0 (= 3 alpha) ".org"))))
358 (should (equal (cons (funcall it) (funcall it)) '(i . 0.0)))
359 (should (equal (funcall it) "\\`[[:alpha:]]\\{3\\}\\.org")))
360
361 (ert-info ("Self-quoting disallowed")
362 (setq it (funcall mex '((0 :a "abc"))))
363 (should (equal (funcall it) 0))
364 (should-error (funcall it)))
365
366 (ert-info ("Global vars and short vars")
367 (setq it (funcall mex '((0 i f erc-nick))))
368 (should (equal (funcall it) 0))
369 (should (equal (funcall it) "foo3foo")))
370
371 (ert-info ("Exits clean")
372 (when (listp (alist-get 'f (erc-d-dialog-vars dialog))) ; may be compiled
373 (should (eq 'closure (car (alist-get 'f (erc-d-dialog-vars dialog))))))
374 (should-not (funcall it))
375 (should (equal (erc-d-dialog-vars dialog)
376 `((:a . 1)
377 (c . ((a b) (: a space b)))
378 (d . (c alpha digit))
379 (bee . 2)
380 (f . ,(alist-get 'f (erc-d-dialog-vars dialog)))
381 (i . erc-nick)))))))
382
383(ert-deftest erc-d--render-entries--matches ()
384 (let* ((alist (list
385 (cons 'f (lambda (a) (funcall a :match 1)))
386 (cons 'g (lambda () (match-string 2 "foo bar baz")))
387 (cons 'h (lambda (a) (concat (funcall a :match 0)
388 (funcall a :request))))
389 (cons 'i (lambda (_ e) (erc-d-exchange-request e)))
390 (cons 'j (lambda ()
391 (set-match-data '(0 1))
392 (match-string 0 "j")))))
393 (dialog (make-erc-d-dialog :vars alist))
394 (exchange (make-erc-d-exchange :dialog dialog
395 :request "foo bar baz"
396 ;; 11 222
397 :match-data '(4 11 4 6 8 11)))
398 (mex (apply-partially #'erc-d-tests--new-ex exchange))
399 it)
400
401 (erc-d-exchange-reload dialog exchange)
402
403 (ert-info ("One arg, match")
404 (setq it (funcall mex '((0 f))))
405 (should (equal (funcall it) 0))
406 (should (equal (funcall it) "ba")))
407
408 (ert-info ("No args")
409 (setq it (funcall mex '((0 g))))
410 (should (equal (funcall it) 0))
411 (should (equal (funcall it) "baz")))
412
413 (ert-info ("Second arg is exchange object")
414 (setq it (funcall mex '((0 i))))
415 (should (equal (funcall it) 0))
416 (should (equal (funcall it) "foo bar baz")))
417
418 (ert-info ("One arg, multiple calls")
419 (setq it (funcall mex '((0 h))))
420 (should (equal (funcall it) 0))
421 (should (equal (funcall it) "bar bazfoo bar baz")))
422
423 (ert-info ("Match data restored")
424 (setq it (funcall mex '((0 j))))
425 (should (equal (funcall it) 0))
426 (should (equal (funcall it) "j"))
427
428 (setq it (funcall mex '((0 g))))
429 (should (equal (funcall it) 0))
430 (should (equal (funcall it) "baz")))
431
432 (ert-info ("Bad signature")
433 (let ((qlist (list 'f '(lambda (p q x) (ignore)))))
434 (setf (erc-d-dialog-vars dialog) qlist)
435 (should-error (erc-d-exchange-reload dialog exchange))))))
436
437(ert-deftest erc-d--render-entries--dynamic ()
438 (let* ((alist (list
439 (cons 'foo "foo")
440 (cons 'f (lambda (a) (funcall a :get-binding 'foo)))
441 (cons 'h (lambda (a) (upcase (funcall a :get-var 'foo))))
442 (cons 'g (lambda (a)
443 (funcall a :rebind 'g (funcall a :get-var 'f))
444 "bar"))
445 (cons 'j (lambda (a) (funcall a :set "123") "abc"))
446 (cons 'k (lambda () "abc"))))
447 (dialog (make-erc-d-dialog :vars alist))
448 (exchange (make-erc-d-exchange :dialog dialog))
449 (mex (apply-partially #'erc-d-tests--new-ex exchange))
450 it)
451
452 (erc-d-exchange-reload dialog exchange)
453
454 (ert-info ("Initial reference calls function")
455 (setq it (funcall mex '((0 j) (0 j))))
456 (should (equal (funcall it) 0))
457 (should (equal (funcall it) "abc")))
458
459 (ert-info ("Subsequent reference expands to string")
460 (should (equal (funcall it) 0))
461 (should (equal (funcall it) "123")))
462
463 (ert-info ("Outside manipulation: initial reference calls function")
464 (setq it (funcall mex '((0 k) (0 k))))
465 (should (equal (funcall it) 0))
466 (should (equal (funcall it) "abc")))
467
468 (ert-info ("Outside manipulation: subsequent reference expands to string")
469 (erc-d-exchange-rebind dialog exchange 'k "123")
470 (should (equal (funcall it) 0))
471 (should (equal (funcall it) "123")))
472
473 (ert-info ("Swap one function for another")
474 (setq it (funcall mex '((0 g) (0 g))))
475 (should (equal (funcall it) 0))
476 (should (equal (funcall it) "bar"))
477 (should (equal (funcall it) 0))
478 (should (equal (funcall it) "foo")))
479
480 (ert-info ("Bindings accessible inside functions")
481 (setq it (funcall mex '((0 f h))))
482 (should (equal (funcall it) 0))
483 (should (equal (funcall it) "fooFOO")))
484
485 (ert-info ("Rebuild alist by sending flag")
486 (setq it (funcall mex '((0 f) (1 f) (2 f) (i 3 f))))
487 (should (equal (funcall it) 0))
488 (should (equal (funcall it) "foo"))
489 (erc-d-exchange-rebind dialog exchange 'f "bar")
490 (should (equal (funcall it) 1))
491 (should (equal (funcall it) "bar"))
492 (setq alist (setf (alist-get 'f (erc-d-dialog-vars dialog))
493 (lambda nil "baz")))
494 (should (eq (funcall it) 2))
495 (should (equal (funcall it 'reload) "baz"))
496 (setq alist (setf (alist-get 'f (erc-d-dialog-vars dialog)) "spam"))
497 (should (eq (funcall it) 'i))
498 (should (eq (funcall it 'reload) 3))
499 (should (equal (funcall it) "\\`spam")))))
500
501(ert-deftest erc-d-t-with-cleanup ()
502 (should-not (get-buffer "*echo*"))
503 (should-not (get-buffer "*foo*"))
504 (should-not (get-buffer "*bar*"))
505 (should-not (get-buffer "*baz*"))
506 (erc-d-t-with-cleanup
507 ((echo (start-process "echo" (get-buffer-create "*echo*") "sleep" "1"))
508 (buffer-foo (get-buffer-create "*foo*"))
509 (buffer-bar (get-buffer-create "*bar*"))
510 (clean-up (list (intern (process-name echo)))) ; let*
511 buffer-baz)
512 (ert-info ("Clean Up")
513 (should (equal clean-up '(ran echo)))
514 (should (bufferp buffer-baz))
515 (should (bufferp buffer-foo))
516 (setq buffer-foo nil))
517 (setq buffer-baz (get-buffer-create "*baz*"))
518 (push 'ran clean-up))
519 (ert-info ("Buffers and procs destroyed")
520 (should-not (get-buffer "*echo*"))
521 (should-not (get-buffer "*bar*"))
522 (should-not (get-buffer "*baz*")))
523 (ert-info ("Buffer foo spared")
524 (should (get-buffer "*foo*"))
525 (kill-buffer "*foo*")))
526
527(ert-deftest erc-d-t-wait-for ()
528 :tags '(:unstable)
529 (let (v)
530 (run-at-time 0.2 nil (lambda () (setq v t)))
531 (should (erc-d-t-wait-for 0.4 "result becomes non-nil" v))
532 (should-error (erc-d-t-wait-for 0.4 "result stays nil" (not v)))
533 (setq v nil)
534 (should-not (erc-d-t-wait-for -0.4 "inverted stays nil" v))
535 (run-at-time 0.2 nil (lambda () (setq v t)))
536 (setq v nil)
537 (should-error (erc-d-t-wait-for -0.4 "inverted becomes non-nil" v))))
538
539(defvar erc-d-tests-with-server-password "changeme")
540
541;; Compromise between removing `autojoin' from `erc-modules' entirely
542;; and allowing side effects to meddle excessively
543(defvar erc-autojoin-channels-alist)
544
545;; This is only meant to be used by tests in this file.
546(cl-defmacro erc-d-tests-with-server ((dumb-server-var erc-server-buffer-var)
547 dialog &rest body)
548 "Create server for DIALOG and run BODY.
549DIALOG may also be a list of dialogs. ERC-SERVER-BUFFER-VAR and
550DUMB-SERVER-VAR are bound accordingly in BODY."
551 (declare (indent 2))
552 (when (eq '_ dumb-server-var)
553 (setq dumb-server-var (make-symbol "dumb-server-var")))
554 (when (eq '_ erc-server-buffer-var)
555 (setq erc-server-buffer-var (make-symbol "erc-server-buffer-var")))
556 (if (listp dialog)
557 (setq dialog (mapcar (lambda (f) (list 'quote f)) dialog))
558 (setq dialog `((quote ,dialog))))
559 `(let* (auth-source-do-cache
560 (,dumb-server-var (erc-d-run "localhost" t ,@dialog))
561 ,erc-server-buffer-var
562 ;;
563 (erc-server-flood-penalty 0.05)
564 erc-autojoin-channels-alist
565 erc-server-auto-reconnect)
566 (should-not erc-d--slow-mo)
567 (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting"))
568 ;; Allow important messages through, even in -batch mode.
569 (advice-add #'erc-handle-login :around #'erc-d-t-silence-around)
570 (advice-add #'erc-server-connect :around #'erc-d-t-silence-around)
571 (unless (or noninteractive erc-debug-irc-protocol)
572 (erc-toggle-debug-irc-protocol))
573 (setq ,erc-server-buffer-var
574 (erc :server "localhost"
575 :password erc-d-tests-with-server-password
576 :port (process-contact ,dumb-server-var :service)
577 :nick "tester"
578 :full-name "tester"))
579 (unwind-protect
580 (progn
581 ,@body
582 (erc-d-t-wait-for 1 "dumb-server death"
583 (not (process-live-p ,dumb-server-var))))
584 (when (process-live-p erc-server-process)
585 (delete-process erc-server-process))
586 (advice-remove #'erc-handle-login #'erc-d-t-silence-around)
587 (advice-remove #'erc-server-connect #'erc-d-t-silence-around)
588 (when noninteractive
589 (kill-buffer ,erc-server-buffer-var)
590 (erc-d-t-kill-related-buffers)))))
591
592(defmacro erc-d-tests-with-failure-spy (found func-syms &rest body)
593 "Wrap functions with advice for inspecting errors caused by BODY.
594Do this for functions whose names appear in FUNC-SYMS. When running
595advice code, add errors to list FOUND. Note: the teardown finalizer is
596not added by default. Also, `erc-d-linger-secs' likely has to be
597nonzero for this to work."
598 (declare (indent 2))
599 ;; Catch errors thrown by timers that `should-error'ignores
600 `(progn
601 (let ((ad (lambda (f o &rest r)
602 (condition-case err
603 (apply o r)
604 (error (push err ,found)
605 (advice-remove f 'spy))))))
606 (dolist (sym ,func-syms)
607 (advice-add sym :around (apply-partially ad sym) '((name . spy)))))
608 (progn ,@body)
609 (dolist (sym ,func-syms)
610 (advice-remove sym 'spy))
611 (setq ,found (nreverse ,found))))
612
613(ert-deftest erc-d-run-nonstandard-messages ()
614 :tags '(:expensive-test)
615 (let* ((erc-d-linger-secs 0.2)
616 (dumb-server (erc-d-run "localhost" t 'nonstandard))
617 (dumb-server-buffer (get-buffer "*erc-d-server*"))
618 (expect (erc-d-t-make-expecter))
619 client)
620 (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting"))
621 (setq client (open-network-stream "erc-d-client" nil
622 "localhost"
623 (process-contact dumb-server :service)
624 :coding 'binary))
625 (ert-info ("Server splits CRLF delimited lines")
626 (process-send-string client "ONE one\r\nTWO two\r\n")
627 (with-current-buffer dumb-server-buffer
628 (funcall expect 1 '(: "<- nonstandard:" (+ digit) " ONE one" eol))
629 (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ TWO two$"))))
630 (ert-info ("Server doesn't discard empty lines")
631 (process-send-string client "\r\n")
632 (with-current-buffer dumb-server-buffer
633 (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ $"))))
634 (ert-info ("Server preserves spaces")
635 (process-send-string client " \r\n")
636 (with-current-buffer dumb-server-buffer
637 (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ \\{2\\}$")))
638 (process-send-string client " \r\n")
639 (with-current-buffer dumb-server-buffer
640 (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ \\{3\\}$"))))
641 (erc-d-t-wait-for 3 "dumb-server death"
642 (not (process-live-p dumb-server)))
643 (delete-process client)
644 (when noninteractive
645 (kill-buffer dumb-server-buffer))))
646
647(ert-deftest erc-d-run-basic ()
648 :tags '(:expensive-test)
649 (erc-d-tests-with-server (_ _) basic
650 (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
651 (erc-d-t-search-for 2 "hey"))
652 (when noninteractive
653 (kill-buffer "#chan"))))
654
655(ert-deftest erc-d-run-eof ()
656 :tags '(:expensive-test)
657 (skip-unless noninteractive)
658 (erc-d-tests-with-server (_ erc-s-buf) eof
659 (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
660 (erc-d-t-search-for 2 "hey"))
661 (with-current-buffer erc-s-buf
662 (process-send-eof erc-server-process))))
663
664(ert-deftest erc-d-run-eof-fail ()
665 :tags '(:expensive-test)
666 (let (errors)
667 (erc-d-tests-with-failure-spy errors '(erc-d--teardown)
668 (erc-d-tests-with-server (_ _) eof
669 (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
670 (erc-d-t-search-for 2 "hey"))
671 (erc-d-t-wait-for 10 errors)))
672 (should (string-match-p "Timed out awaiting request.*__EOF__"
673 (cadr (pop errors))))))
674
675(ert-deftest erc-d-run-linger ()
676 :tags '(:expensive-test)
677 (erc-d-tests-with-server (dumb-s _) linger
678 (with-current-buffer (erc-d-t-wait-for 6 (get-buffer "#chan"))
679 (erc-d-t-search-for 2 "hey"))
680 (with-current-buffer (process-buffer dumb-s)
681 (erc-d-t-search-for 2 "Lingering for 1.00 seconds"))
682 (with-current-buffer (process-buffer dumb-s)
683 (erc-d-t-search-for 3 "Lingered for 1.00 seconds"))))
684
685(ert-deftest erc-d-run-linger-fail ()
686 :tags '(:expensive-test)
687 (let ((erc-server-flood-penalty 0.1)
688 errors)
689 (erc-d-tests-with-failure-spy
690 errors '(erc-d--teardown erc-d-command)
691 (erc-d-tests-with-server (_ _) linger
692 (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
693 (erc-d-t-search-for 2 "hey")
694 (erc-cmd-MSG "#chan hi"))
695 (erc-d-t-wait-for 10 "Bad match" errors)))
696 (should (string-match-p "Match failed.*hi" (cadr (pop errors))))))
697
698(ert-deftest erc-d-run-linger-direct ()
699 :tags '(:expensive-test)
700 (let* ((dumb-server (erc-d-run "localhost" t
701 'linger-multi-a 'linger-multi-b))
702 (port (process-contact dumb-server :service))
703 (dumb-server-buffer (get-buffer "*erc-d-server*"))
704 (client-buffer-a (get-buffer-create "*erc-d-client-a*"))
705 (client-buffer-b (get-buffer-create "*erc-d-client-b*"))
706 (start (current-time))
707 client-a client-b)
708 (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting"))
709 (setq client-a (open-network-stream "erc-d-client-a" client-buffer-a
710 "localhost" port
711 :coding 'binary)
712 client-b (open-network-stream "erc-d-client-b" client-buffer-b
713 "localhost" port
714 :coding 'binary))
715 (process-send-string client-a "PASS :a\r\n")
716 (sleep-for 0.01)
717 (process-send-string client-b "PASS :b\r\n")
718 (sleep-for 0.01)
719 (erc-d-t-wait-for 3 "dumb-server death"
720 (not (process-live-p dumb-server)))
721 (ert-info ("Ensure linger of one second")
722 (should (time-less-p 1 (time-subtract (current-time) start)))
723 (should (time-less-p (time-subtract (current-time) start) 1.5)))
724 (delete-process client-a)
725 (delete-process client-b)
726 (when noninteractive
727 (kill-buffer client-buffer-a)
728 (kill-buffer client-buffer-b)
729 (kill-buffer dumb-server-buffer))))
730
731(ert-deftest erc-d-run-drop-direct ()
732 :tags '(:unstable)
733 (let* ((dumb-server (erc-d-run "localhost" t 'drop-a 'drop-b))
734 (port (process-contact dumb-server :service))
735 (dumb-server-buffer (get-buffer "*erc-d-server*"))
736 (client-buffer-a (get-buffer-create "*erc-d-client-a*"))
737 (client-buffer-b (get-buffer-create "*erc-d-client-b*"))
738 (start (current-time))
739 client-a client-b)
740 (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting"))
741 (setq client-a (open-network-stream "erc-d-client-a" client-buffer-a
742 "localhost" port
743 :coding 'binary)
744 client-b (open-network-stream "erc-d-client-b" client-buffer-b
745 "localhost" port
746 :coding 'binary))
747 (process-send-string client-a "PASS :a\r\n")
748 (sleep-for 0.01)
749 (process-send-string client-b "PASS :b\r\n")
750 (erc-d-t-wait-for 3 "client-a dies" (not (process-live-p client-a)))
751 (should (time-less-p (time-subtract (current-time) start) 0.32))
752 (erc-d-t-wait-for 3 "dumb-server death"
753 (not (process-live-p dumb-server)))
754 (ert-info ("Ensure linger of one second")
755 (should (time-less-p 1 (time-subtract (current-time) start))))
756 (delete-process client-a)
757 (delete-process client-b)
758 (when noninteractive
759 (kill-buffer client-buffer-a)
760 (kill-buffer client-buffer-b)
761 (kill-buffer dumb-server-buffer))))
762
763(ert-deftest erc-d-run-no-match ()
764 :tags '(:expensive-test)
765 (let ((erc-d-linger-secs 1)
766 erc-server-auto-reconnect
767 errors)
768 (erc-d-tests-with-failure-spy errors '(erc-d--teardown erc-d-command)
769 (erc-d-tests-with-server (_ erc-server-buffer) no-match
770 (with-current-buffer erc-server-buffer
771 (erc-d-t-search-for 2 "away")
772 (erc-cmd-JOIN "#foo")
773 (erc-d-t-wait-for 10 "Bad match" errors))))
774 (should (string-match-p "Match failed.*foo.*chan" (cadr (pop errors))))
775 (should-not (get-buffer "#foo"))))
776
777(ert-deftest erc-d-run-timeout ()
778 :tags '(:expensive-test)
779 (let ((erc-d-linger-secs 1)
780 err errors)
781 (erc-d-tests-with-failure-spy errors '(erc-d--teardown)
782 (erc-d-tests-with-server (_ _) timeout
783 (erc-d-t-wait-for 10 "error caught" errors)))
784 (setq err (pop errors))
785 (should (eq (car err) 'erc-d-timeout))
786 (should (string-match-p "Timed out" (cadr err)))))
787
788(ert-deftest erc-d-run-unexpected ()
789 :tags '(:expensive-test)
790 (let ((erc-d-linger-secs 2)
791 errors)
792 (erc-d-tests-with-failure-spy
793 errors '(erc-d--teardown erc-d-command)
794 (erc-d-tests-with-server (_ _) unexpected
795 (ert-info ("All specs consumed when more input arrives")
796 (erc-d-t-wait-for 10 "error caught" (cdr errors)))))
797 (should (string-match-p "unexpected.*MODE" (cadr (pop errors))))
798 ;; Nonsensical normally because func would have already exited when
799 ;; first error was thrown
800 (should (string-match-p "Match failed" (cadr (pop errors))))))
801
802(ert-deftest erc-d-run-unexpected-depleted ()
803 :tags '(:expensive-test)
804 (let ((erc-d-linger-secs 3)
805 errors)
806 (erc-d-tests-with-failure-spy errors '(erc-d--teardown erc-d-command)
807 (let* ((dumb-server-buffer (get-buffer-create "*erc-d-server*"))
808 (dumb-server (erc-d-run "localhost" t 'depleted))
809 (expect (erc-d-t-make-expecter))
810 (client-buf (get-buffer-create "*erc-d-client*"))
811 client-proc)
812 (with-current-buffer dumb-server-buffer
813 (erc-d-t-search-for 3 "Starting"))
814 (setq client-proc (make-network-process
815 :buffer client-buf
816 :name "erc-d-client"
817 :family 'ipv4
818 :noquery t
819 :coding 'binary
820 :service (process-contact dumb-server :service)
821 :host "localhost"))
822 (with-current-buffer dumb-server-buffer
823 (funcall expect 3 "open from"))
824 (process-send-string client-proc "PASS :changeme\r\n")
825 (sleep-for 0.01)
826 (process-send-string client-proc "NICK tester\r\n")
827 (sleep-for 0.01)
828 (process-send-string client-proc "USER user 0 * :tester\r\n")
829 (sleep-for 0.01)
830 (when (process-live-p client-proc)
831 (process-send-string client-proc "BLAH :too much\r\n")
832 (sleep-for 0.01))
833 (with-current-buffer client-buf
834 (funcall expect 3 "Welcome to the Internet"))
835 (erc-d-t-wait-for 2 "dumb-server death"
836 (not (process-live-p dumb-server)))
837 (delete-process client-proc)
838 (when noninteractive
839 (kill-buffer client-buf)
840 (kill-buffer dumb-server-buffer))))
841 (should (string-match-p "unexpected.*BLAH" (cadr (pop errors))))
842 ;; Wouldn't happen IRL
843 (should (string-match-p "unexpected.*BLAH" (cadr (pop errors))))
844 (should-not errors)))
845
846(defun erc-d-tests--dynamic-match-user (_dialog exchange)
847 "Shared pattern/response handler for canned dynamic DIALOG test."
848 (should (string= (match-string 1 (erc-d-exchange-request exchange))
849 "tester")))
850
851(defun erc-d-tests--run-dynamic ()
852 "Perform common assertions for \"dynamic\" dialog."
853 (erc-d-tests-with-server (dumb-server erc-server-buffer) dynamic
854 (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
855 (erc-d-t-search-for 2 "tester: hey"))
856 (with-current-buffer erc-server-buffer
857 (let ((expect (erc-d-t-make-expecter)))
858 (funcall expect 2 "host is irc.fsf.org")
859 (funcall expect 2 "modes for tester")))
860 (with-current-buffer (process-buffer dumb-server)
861 (erc-d-t-search-for 2 "irc.fsf.org"))
862 (when noninteractive
863 (kill-buffer "#chan"))))
864
865(ert-deftest erc-d-run-dynamic-default-match ()
866 :tags '(:expensive-test)
867 (let* (dynamic-tally
868 (erc-d-tmpl-vars '((user . "user")
869 (ignored . ((a b) (: a space b)))
870 (realname . (group (+ graph)))))
871 (nick (lambda (a)
872 (push '(nick . match-user) dynamic-tally)
873 (funcall a :set (funcall a :match 1) 'export)))
874 (dom (lambda (a)
875 (push '(dom . match-user) dynamic-tally)
876 (funcall a :set erc-d-server-fqdn)))
877 (erc-d-match-handlers
878 (list :user (lambda (d e)
879 (erc-d-exchange-rebind d e 'nick nick)
880 (erc-d-exchange-rebind d e 'dom dom)
881 (erc-d-tests--dynamic-match-user d e))
882 :mode-user (lambda (d e)
883 (erc-d-exchange-rebind d e 'nick "tester")
884 (erc-d-exchange-rebind d e 'dom dom))))
885 (erc-d-server-fqdn "irc.fsf.org"))
886 (erc-d-tests--run-dynamic)
887 (should (equal '((dom . match-user) (nick . match-user) (dom . match-user))
888 dynamic-tally))))
889
890(ert-deftest erc-d-run-dynamic-default-match-rebind ()
891 :tags '(:expensive-test)
892 (let* (tally
893 ;;
894 (erc-d-tmpl-vars '((user . "user")
895 (ignored . ((a b) (: a space b)))
896 (realname . (group (+ graph)))))
897 (erc-d-match-handlers
898 (list :user
899 (lambda (d e)
900 (erc-d-exchange-rebind
901 d e 'nick
902 (lambda (a)
903 (push 'bind-nick tally)
904 (funcall a :rebind 'nick (funcall a :match 1) 'export)))
905 (erc-d-exchange-rebind
906 d e 'dom
907 (lambda ()
908 (push 'bind-dom tally)
909 (erc-d-exchange-rebind d e 'dom erc-d-server-fqdn)))
910 (erc-d-tests--dynamic-match-user d e))
911 :mode-user
912 (lambda (d e)
913 (erc-d-exchange-rebind d e 'nick "tester")
914 (erc-d-exchange-rebind d e 'dom erc-d-server-fqdn))))
915 (erc-d-server-fqdn "irc.fsf.org"))
916 (erc-d-tests--run-dynamic)
917 (should (equal '(bind-nick bind-dom) tally))))
918
919(ert-deftest erc-d-run-dynamic-runtime-stub ()
920 :tags '(:expensive-test)
921 (let ((erc-d-tmpl-vars '((token . (group (or "barnet" "foonet")))))
922 (erc-d-match-handlers
923 (list :pass (lambda (d _e)
924 (erc-d-load-replacement-dialog d 'dynamic-foonet))))
925 (erc-d-tests-with-server-password "foonet:changeme"))
926 (erc-d-tests-with-server (_ erc-server-buffer)
927 (dynamic-stub dynamic-foonet)
928 (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
929 (erc-d-t-search-for 2 "alice:")
930 (erc-d-t-absent-for 0.1 "joe"))
931 (with-current-buffer erc-server-buffer
932 (let ((expect (erc-d-t-make-expecter)))
933 (funcall expect 2 "host is irc.foonet.org")
934 (funcall expect 2 "NETWORK=FooNet")))
935 (when noninteractive
936 (kill-buffer "#chan")))))
937
938(ert-deftest erc-d-run-dynamic-runtime-stub-skip ()
939 :tags '(:expensive-test)
940 (let ((erc-d-tmpl-vars '((token . "barnet")))
941 (erc-d-match-handlers
942 (list :pass (lambda (d _e)
943 (erc-d-load-replacement-dialog
944 d 'dynamic-barnet 1))))
945 (erc-d-tests-with-server-password "barnet:changeme"))
946 (erc-d-tests-with-server (_ erc-server-buffer)
947 (dynamic-stub dynamic-barnet)
948 (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
949 (erc-d-t-search-for 2 "joe:")
950 (erc-d-t-absent-for 0.1 "alice"))
951 (with-current-buffer erc-server-buffer
952 (let ((expect (erc-d-t-make-expecter)))
953 (funcall expect 2 "host is irc.barnet.org")
954 (funcall expect 2 "NETWORK=BarNet")))
955 (when noninteractive
956 (kill-buffer "#chan")))))
957
958;; Two servers, in-process, one client per
959(ert-deftest erc-d-run-dual-direct ()
960 :tags '(:expensive-test)
961 (let* ((erc-d--slow-mo -1)
962 (server-a (erc-d-run "localhost" t "erc-d-server-a" 'dynamic-foonet))
963 (server-b (erc-d-run "localhost" t "erc-d-server-b" 'dynamic-barnet))
964 (server-a-buffer (get-buffer "*erc-d-server-a*"))
965 (server-b-buffer (get-buffer "*erc-d-server-b*"))
966 (client-a-buffer (get-buffer-create "*erc-d-client-a*"))
967 (client-b-buffer (get-buffer-create "*erc-d-client-b*"))
968 client-a client-b)
969 (with-current-buffer server-a-buffer (erc-d-t-search-for 4 "Starting"))
970 (with-current-buffer server-b-buffer (erc-d-t-search-for 4 "Starting"))
971 (setq client-a (make-network-process
972 :buffer client-a-buffer
973 :name "erc-d-client-a"
974 :family 'ipv4
975 :noquery t
976 :coding 'binary
977 :service (process-contact server-a :service)
978 :host "localhost")
979 client-b (make-network-process
980 :buffer client-b-buffer
981 :name "erc-d-client-b"
982 :family 'ipv4
983 :noquery t
984 :coding 'binary
985 :service (process-contact server-b :service)
986 :host "localhost"))
987 ;; Also tests slo-mo indirectly because FAKE would fail without it
988 (process-send-string client-a "NICK tester\r\n")
989 (process-send-string client-b "FAKE noop\r\nNICK tester\r\n")
990 (sleep-for 0.01)
991 (process-send-string client-a "USER user 0 * :tester\r\n")
992 (process-send-string client-b "USER user 0 * :tester\r\n")
993 (sleep-for 0.01)
994 (process-send-string client-a "MODE tester +i\r\n")
995 (process-send-string client-b "MODE tester +i\r\n")
996 (sleep-for 0.01)
997 (process-send-string client-a "MODE #chan\r\n")
998 (process-send-string client-b "MODE #chan\r\n")
999 (sleep-for 0.01)
1000 (erc-d-t-wait-for 2 "server-a death" (not (process-live-p server-a)))
1001 (erc-d-t-wait-for 2 "server-b death" (not (process-live-p server-b)))
1002 (when noninteractive
1003 (kill-buffer client-a-buffer)
1004 (kill-buffer client-b-buffer)
1005 (kill-buffer server-a-buffer)
1006 (kill-buffer server-b-buffer))))
1007
1008;; This can be removed; only exists to get a baseline for next test
1009(ert-deftest erc-d-run-fuzzy-direct ()
1010 :tags '(:expensive-test)
1011 (let* ((erc-d-tmpl-vars
1012 `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t)))))
1013 (dumb-server (erc-d-run "localhost" t 'fuzzy))
1014 (dumb-server-buffer (get-buffer "*erc-d-server*"))
1015 (client-buffer (get-buffer-create "*erc-d-client*"))
1016 client)
1017 (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting"))
1018 (setq client (make-network-process
1019 :buffer client-buffer
1020 :name "erc-d-client"
1021 :family 'ipv4
1022 :noquery t
1023 :coding 'binary
1024 :service (process-contact dumb-server :service)
1025 :host "localhost"))
1026 ;; We could also just send this as a single fatty
1027 (process-send-string client "PASS :changeme\r\n")
1028 (sleep-for 0.01)
1029 (process-send-string client "NICK tester\r\n")
1030 (sleep-for 0.01)
1031 (process-send-string client "USER user 0 * :tester\r\n")
1032 (sleep-for 0.01)
1033 (process-send-string client "MODE tester +i\r\n")
1034 (sleep-for 0.01)
1035 (process-send-string client "JOIN #bar\r\n")
1036 (sleep-for 0.01)
1037 (process-send-string client "JOIN #foo\r\n")
1038 (sleep-for 0.01)
1039 (process-send-string client "MODE #bar\r\n")
1040 (sleep-for 0.01)
1041 (process-send-string client "MODE #foo\r\n")
1042 (sleep-for 0.01)
1043 (erc-d-t-wait-for 1 "dumb-server death"
1044 (not (process-live-p dumb-server)))
1045 (when noninteractive
1046 (kill-buffer client-buffer)
1047 (kill-buffer dumb-server-buffer))))
1048
1049;; Without adjusting penalty, takes ~15 secs. With is comprable to direct ^.
1050(ert-deftest erc-d-run-fuzzy ()
1051 :tags '(:expensive-test)
1052 (let ((erc-server-flood-penalty 1.2) ; penalty < margin/sends is basically 0
1053 (erc-d-linger-secs 0.1)
1054 (erc-d-tmpl-vars
1055 `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t)))))
1056 erc-server-auto-reconnect)
1057 (erc-d-tests-with-server (_ erc-server-buffer) fuzzy
1058 (with-current-buffer erc-server-buffer
1059 (erc-d-t-search-for 2 "away")
1060 (goto-char erc-input-marker)
1061 (erc-cmd-JOIN "#bar"))
1062 (erc-d-t-wait-for 2 (get-buffer "#bar"))
1063 (with-current-buffer erc-server-buffer
1064 (erc-cmd-JOIN "#foo"))
1065 (erc-d-t-wait-for 20 (get-buffer "#foo"))
1066 (with-current-buffer "#bar"
1067 (erc-d-t-search-for 1 "was created on"))
1068 (with-current-buffer "#foo"
1069 (erc-d-t-search-for 5 "was created on")))))
1070
1071(ert-deftest erc-d-run-no-block ()
1072 :tags '(:expensive-test)
1073 (let ((erc-server-flood-penalty 1)
1074 (erc-d-linger-secs 1.2)
1075 (expect (erc-d-t-make-expecter))
1076 erc-server-auto-reconnect)
1077 (erc-d-tests-with-server (_ erc-server-buffer) no-block
1078 (with-current-buffer erc-server-buffer
1079 (funcall expect 2 "away")
1080 (funcall expect 1 erc-prompt)
1081 (with-current-buffer erc-server-buffer (erc-cmd-JOIN "#foo")))
1082 (with-current-buffer (erc-d-t-wait-for 2 (get-buffer "#foo"))
1083 (funcall expect 2 "was created on"))
1084
1085 (ert-info ("Join #bar")
1086 (with-current-buffer erc-server-buffer (erc-cmd-JOIN "#bar"))
1087 (erc-d-t-wait-for 2 (get-buffer "#bar")))
1088
1089 (with-current-buffer "#bar" (funcall expect 1 "was created on"))
1090
1091 (ert-info ("Server expects next pattern but keeps sending")
1092 (with-current-buffer "#foo" (funcall expect 2 "Rosalind, I will "))
1093 (with-current-buffer "#bar" (funcall expect 1 "hi 123"))
1094 (with-current-buffer "#foo"
1095 (should-not (search-forward "<bob> I am heard" nil t))
1096 (funcall expect 1.5 "<bob> I am heard"))))))
1097
1098(defun erc-d-tests--run-proxy-direct (dumb-server dumb-server-buffer port)
1099 "Start DUMB-SERVER with DUMB-SERVER-BUFFER and PORT.
1100These are steps shared by in-proc and subproc variants testing a
1101bouncer-like setup."
1102 (when (version< emacs-version "28") (ert-skip "TODO connection refused"))
1103 (let ((client-buffer-foo (get-buffer-create "*erc-d-client-foo*"))
1104 (client-buffer-bar (get-buffer-create "*erc-d-client-bar*"))
1105 (expect (erc-d-t-make-expecter))
1106 client-foo
1107 client-bar)
1108 (setq client-foo (make-network-process
1109 :buffer client-buffer-foo
1110 :name "erc-d-client-foo"
1111 :family 'ipv4
1112 :noquery t
1113 :coding 'binary
1114 :service port
1115 :host "localhost")
1116 client-bar (make-network-process
1117 :buffer client-buffer-bar
1118 :name "erc-d-client-bar"
1119 :family 'ipv4
1120 :noquery t
1121 :coding 'binary
1122 :service port
1123 :host "localhost"))
1124 (with-current-buffer dumb-server-buffer
1125 (funcall expect 3 "open from"))
1126 (process-send-string client-foo "PASS :foo:changeme\r\n")
1127 (process-send-string client-bar "PASS :bar:changeme\r\n")
1128 (sleep-for 0.01)
1129 (process-send-string client-foo "NICK tester\r\n")
1130 (process-send-string client-bar "NICK tester\r\n")
1131 (sleep-for 0.01)
1132 (process-send-string client-foo "USER user 0 * :tester\r\n")
1133 (process-send-string client-bar "USER user 0 * :tester\r\n")
1134 (sleep-for 0.01)
1135 (process-send-string client-foo "MODE tester +i\r\n")
1136 (process-send-string client-bar "MODE tester +i\r\n")
1137 (sleep-for 0.01)
1138 (with-current-buffer client-buffer-foo
1139 (funcall expect 3 "FooNet")
1140 (funcall expect 3 "irc.foo.net")
1141 (funcall expect 3 "marked as being away")
1142 (goto-char (point-min))
1143 (should-not (search-forward "bar" nil t)))
1144 (with-current-buffer client-buffer-bar
1145 (funcall expect 3 "BarNet")
1146 (funcall expect 3 "irc.bar.net")
1147 (funcall expect 3 "marked as being away")
1148 (goto-char (point-min))
1149 (should-not (search-forward "foo" nil t)))
1150 (erc-d-t-wait-for 2 "dumb-server death"
1151 (not (process-live-p dumb-server)))
1152 (delete-process client-foo)
1153 (delete-process client-bar)
1154 (when noninteractive
1155 (kill-buffer client-buffer-foo)
1156 (kill-buffer client-buffer-bar)
1157 (kill-buffer dumb-server-buffer))))
1158
1159;; This test shows the simplest way to set up template variables: put
1160;; everything needed for the whole session in `erc-d-tmpl-vars' before
1161;; starting the server.
1162
1163(ert-deftest erc-d-run-proxy-direct-spec-vars ()
1164 :tags '(:expensive-test)
1165 (let* ((dumb-server-buffer (get-buffer-create "*erc-d-server*"))
1166 (erc-d-linger-secs 0.5)
1167 (erc-d-tmpl-vars
1168 `((network . (group (+ alpha)))
1169 (fqdn . ,(lambda (a)
1170 (let ((network (funcall a :match 1 'pass)))
1171 (should (member network '("foo" "bar")))
1172 (funcall a :set (concat "irc." network ".net")))))
1173 (net . ,(lambda (a)
1174 (let ((network (funcall a :match 1 'pass)))
1175 (should (member network '("foo" "bar")))
1176 (concat (capitalize network) "Net"))))))
1177 (dumb-server (erc-d-run "localhost" t 'proxy-foonet 'proxy-barnet))
1178 (port (process-contact dumb-server :service)))
1179 (with-current-buffer dumb-server-buffer
1180 (erc-d-t-search-for 3 "Starting"))
1181 (erc-d-tests--run-proxy-direct dumb-server dumb-server-buffer port)))
1182
1183(cl-defun erc-d-tests--start-server (&key dialogs buffer linger program libs)
1184 "Start and return a server in a subprocess using BUFFER and PORT.
1185DIALOGS are symbols representing the base names of dialog files in
1186`erc-d-u-canned-dialog-dir'. LIBS are extra files to load."
1187 (push (locate-library "erc-d" nil (list erc-d-u--library-directory)) libs)
1188 (cl-assert (car libs))
1189 (let* ((args `("erc-d-server" ,buffer
1190 ,(concat invocation-directory invocation-name)
1191 "-Q" "-batch" "-L" ,erc-d-u--library-directory
1192 ,@(let (o) (while libs (push (pop libs) o) (push "-l" o)) o)
1193 "-eval" ,(format "%S" program) "-f" "erc-d-serve"
1194 ,@(when linger (list "--linger" (number-to-string linger)))
1195 ,@(mapcar #'erc-d-u--expand-dialog-symbol dialogs)))
1196 (proc (apply #'start-process args)))
1197 (set-process-query-on-exit-flag proc nil)
1198 (with-current-buffer buffer
1199 (erc-d-t-search-for 5 "Starting")
1200 (search-forward " (")
1201 (backward-char))
1202 (let ((pair (read buffer)))
1203 (cons proc (cdr pair)))))
1204
1205(ert-deftest erc-d-run-proxy-direct-subprocess ()
1206 :tags '(:expensive-test)
1207 (let* ((buffer (get-buffer-create "*erc-d-server*"))
1208 ;; These are quoted because they're passed as printed forms to subproc
1209 (fqdn '(lambda (a e)
1210 (let* ((d (erc-d-exchange-dialog e))
1211 (name (erc-d-dialog-name d)))
1212 (funcall a :set (if (eq name 'proxy-foonet)
1213 "irc.foo.net"
1214 "irc.bar.net")))))
1215 (net '(lambda (a)
1216 (funcall a :rebind 'net
1217 (if (eq (funcall a :dialog-name) 'proxy-foonet)
1218 "FooNet"
1219 "BarNet"))))
1220 (program `(setq erc-d-tmpl-vars '((fqdn . ,fqdn)
1221 (net . ,net)
1222 (network . (group (+ alpha))))))
1223 (port (erc-d-tests--start-server
1224 :linger 0.3
1225 :program program
1226 :buffer buffer
1227 :dialogs '(proxy-foonet proxy-barnet)))
1228 (server (pop port)))
1229 (erc-d-tests--run-proxy-direct server buffer port)))
1230
1231(ert-deftest erc-d-run-proxy-direct-subprocess-lib ()
1232 :tags '(:expensive-test)
1233 (let* ((buffer (get-buffer-create "*erc-d-server*"))
1234 (lib (expand-file-name "proxy-subprocess.el"
1235 (ert-resource-directory)))
1236 (port (erc-d-tests--start-server :linger 0.3
1237 :buffer buffer
1238 :dialogs '(proxy-foonet proxy-barnet)
1239 :libs (list lib)))
1240 (server (pop port)))
1241 (erc-d-tests--run-proxy-direct server buffer port)))
1242
1243(ert-deftest erc-d-run-no-pong ()
1244 :tags '(:expensive-test)
1245 (let* (erc-d-auto-pong
1246 ;;
1247 (erc-d-tmpl-vars
1248 `((nonce . (group (: digit digit)))
1249 (echo . ,(lambda (a)
1250 (should (string= (funcall a :match 1) "42")) "42"))))
1251 (dumb-server-buffer (get-buffer-create "*erc-d-server*"))
1252 (dumb-server (erc-d-run "localhost" t 'no-pong))
1253 (expect (erc-d-t-make-expecter))
1254 (client-buf (get-buffer-create "*erc-d-client*"))
1255 client-proc)
1256 (with-current-buffer dumb-server-buffer
1257 (erc-d-t-search-for 3 "Starting"))
1258 (setq client-proc (make-network-process
1259 :buffer client-buf
1260 :name "erc-d-client"
1261 :family 'ipv4
1262 :noquery t
1263 :coding 'binary
1264 :service (process-contact dumb-server :service)
1265 :host "localhost"))
1266 (with-current-buffer dumb-server-buffer
1267 (funcall expect 3 "open from"))
1268 (process-send-string client-proc "PASS :changeme\r\nNICK tester\r\n")
1269 (sleep-for 0.01)
1270 (process-send-string client-proc "USER user 0 * :tester\r\n")
1271 (sleep-for 0.01)
1272 (process-send-string client-proc "MODE tester +i\r\n")
1273 (sleep-for 0.01)
1274 (with-current-buffer client-buf
1275 (funcall expect 3 "ExampleOrg")
1276 (funcall expect 3 "irc.example.org")
1277 (funcall expect 3 "marked as being away"))
1278 (ert-info ("PING is not intercepted by specialized method")
1279 (process-send-string client-proc "PING 42\r\n")
1280 (with-current-buffer client-buf
1281 (funcall expect 3 "PONG")))
1282 (erc-d-t-wait-for 2 "dumb-server death"
1283 (not (process-live-p dumb-server)))
1284 (delete-process client-proc)
1285 (when noninteractive
1286 (kill-buffer client-buf)
1287 (kill-buffer dumb-server-buffer))))
1288
1289;; Inspect replies as they arrive within a single exchange, i.e., ensure we
1290;; don't regress to prior buggy version in which inspection wasn't possible
1291;; until all replies had been sent by the server.
1292(ert-deftest erc-d-run-incremental ()
1293 :tags '(:expensive-test)
1294 (let ((erc-server-flood-penalty 0)
1295 (expect (erc-d-t-make-expecter))
1296 erc-d-linger-secs)
1297 (erc-d-tests-with-server (_ erc-server-buffer) incremental
1298 (with-current-buffer erc-server-buffer
1299 (funcall expect 3 "marked as being away"))
1300 (with-current-buffer erc-server-buffer
1301 (erc-cmd-JOIN "#foo"))
1302 (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
1303 (funcall expect 1 "Users on #foo")
1304 (funcall expect 1 "Look for me")
1305 (not (search-forward "Done" nil t))
1306 (funcall expect 10 "Done")
1307 (erc-send-message "Hi")))))
1308
1309(ert-deftest erc-d-unix-socket-direct ()
1310 :tags '(:expensive-test)
1311 (skip-unless (featurep 'make-network-process '(:family local)))
1312 (let* ((erc-d-linger-secs 0.1)
1313 (sock (expand-file-name "erc-d.sock" temporary-file-directory))
1314 (dumb-server (erc-d-run nil sock 'basic))
1315 (dumb-server-buffer (get-buffer "*erc-d-server*"))
1316 (client-buffer (get-buffer-create "*erc-d-client*"))
1317 client)
1318 (with-current-buffer "*erc-d-server*"
1319 (erc-d-t-search-for 4 "Starting"))
1320 (unwind-protect
1321 (progn
1322 (setq client (make-network-process
1323 :buffer client-buffer
1324 :name "erc-d-client"
1325 :family 'local
1326 :noquery t
1327 :coding 'binary
1328 :service sock))
1329 (process-send-string client "PASS :changeme\r\n")
1330 (sleep-for 0.01)
1331 (process-send-string client "NICK tester\r\n")
1332 (sleep-for 0.01)
1333 (process-send-string client "USER user 0 * :tester\r\n")
1334 (sleep-for 0.1)
1335 (process-send-string client "MODE tester +i\r\n")
1336 (sleep-for 0.01)
1337 (process-send-string client "MODE #chan\r\n")
1338 (sleep-for 0.01)
1339 (erc-d-t-wait-for 1 "dumb-server death"
1340 (not (process-live-p dumb-server)))
1341 (when noninteractive
1342 (kill-buffer client-buffer)
1343 (kill-buffer dumb-server-buffer)))
1344 (delete-file sock))))
1345
1346;;; erc-d-tests.el ends here
diff --git a/test/lisp/erc/resources/erc-d/erc-d-u.el b/test/lisp/erc/resources/erc-d/erc-d-u.el
new file mode 100644
index 00000000000..ce13efef624
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/erc-d-u.el
@@ -0,0 +1,213 @@
1;;; erc-d-u.el --- Helpers for ERC test server -*- lexical-binding: t -*-
2
3;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
4;;
5;; This file is part of GNU Emacs.
6;;
7;; This program is free software: you can redistribute it and/or
8;; modify it under the terms of the GNU General Public License as
9;; published by the Free Software Foundation, either version 3 of the
10;; License, or (at your option) any later version.
11;;
12;; This program is distributed in the hope that it will be useful, but
13;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15;; General Public License for more details.
16;;
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see
19;; <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;; The utilities here are kept separate from those in `erc-d' so that
24;; tests running the server in a subprocess can use them without
25;; having to require the main lib. If migrating outside of test/lisp,
26;; there may be no reason to continue this.
27;;
28;; Another (perhaps misguided) goal here is to avoid having ERC itself
29;; as a dependency.
30;;
31;; FIXME this ^ is no longer the case (ERC is not a dependency)
32
33;;; Code:
34(require 'rx)
35(require 'subr-x)
36(eval-when-compile (require 'ert))
37
38(defvar erc-d-u--canned-buffers nil
39 "List of canned dialog buffers currently open for reading.")
40
41(cl-defstruct (erc-d-u-scan-d) ; dialog scanner
42 (buf nil :type buffer)
43 (done nil :type boolean)
44 (last nil :type integer)
45 (hunks nil :type (list-of marker))
46 (f #'erc-d-u--read-exchange-default :type function))
47
48(cl-defstruct (erc-d-u-scan-e) ; exchange scanner
49 (sd nil :type erc-d-u-scan-d)
50 (pos nil :type marker))
51
52(defun erc-d-u--read-dialog (info)
53 "Read dialog file and stash relevant state in `erc-d-u-scan-d' INFO."
54 (if (and (buffer-live-p (erc-d-u-scan-d-buf info))
55 (with-current-buffer (erc-d-u-scan-d-buf info)
56 (condition-case _err
57 (progn
58 (when (erc-d-u-scan-d-last info)
59 (goto-char (erc-d-u-scan-d-last info))
60 (forward-list))
61 (setf (erc-d-u-scan-d-last info) (point))
62 (down-list)
63 (push (set-marker (make-marker) (point))
64 (erc-d-u-scan-d-hunks info)))
65 ((end-of-buffer scan-error)
66 (setf (erc-d-u-scan-d-done info) t)
67 nil))))
68 (make-erc-d-u-scan-e :sd info :pos (car (erc-d-u-scan-d-hunks info)))
69 (unless (erc-d-u-scan-d-hunks info)
70 (kill-buffer (erc-d-u-scan-d-buf info))
71 nil)))
72
73(defun erc-d-u--read-exchange-default (info)
74 "Read from marker in exchange `erc-d-u-scan-e' object INFO."
75 (let ((hunks (erc-d-u-scan-e-sd info))
76 (pos (erc-d-u-scan-e-pos info)))
77 (or (and (erc-d-u-scan-d-hunks hunks)
78 (with-current-buffer (erc-d-u-scan-d-buf hunks)
79 (goto-char pos)
80 (condition-case _err
81 (read pos)
82 ;; Raised unless malformed
83 (invalid-read-syntax
84 nil))))
85 (unless (or (cl-callf (lambda (s) (delq pos s)) ; flip
86 (erc-d-u-scan-d-hunks hunks))
87 (not (erc-d-u-scan-d-done hunks)))
88 (kill-buffer (erc-d-u-scan-d-buf hunks))
89 nil))))
90
91(defun erc-d-u--read-exchange (info)
92 "Call exchange reader assigned in `erc-d-u-scan-e' object INFO."
93 (funcall (erc-d-u-scan-d-f (erc-d-u-scan-e-sd info)) info))
94
95(defun erc-d-u--canned-read (file)
96 "Dispense a reader for each exchange in dialog FILE."
97 (let ((buf (generate-new-buffer (file-name-nondirectory file))))
98 (push buf erc-d-u--canned-buffers)
99 (with-current-buffer buf
100 (setq-local parse-sexp-ignore-comments t
101 coding-system-for-read 'utf-8)
102 (add-hook 'kill-buffer-hook
103 (lambda () (setq erc-d-u--canned-buffers
104 (delq buf erc-d-u--canned-buffers)))
105 nil 'local)
106 (insert-file-contents-literally file)
107 (lisp-data-mode))
108 (make-erc-d-u-scan-d :buf buf)))
109
110(defvar erc-d-u--library-directory (file-name-directory load-file-name))
111(defvar erc-d-u-canned-dialog-dir
112 (file-name-as-directory (expand-file-name "resources"
113 erc-d-u--library-directory)))
114
115(defun erc-d-u--normalize-canned-name (dialog)
116 "Return DIALOG name as a symbol without validating it."
117 (if (symbolp dialog)
118 dialog
119 (intern (file-name-base dialog))))
120
121(defvar erc-d-u-canned-file-name-extension ".eld")
122
123(defun erc-d-u--expand-dialog-symbol (dialog)
124 "Return filename based on symbol DIALOG."
125 (let ((name (symbol-name dialog)))
126 (unless (equal (file-name-extension name)
127 erc-d-u-canned-file-name-extension)
128 (setq name (concat name erc-d-u-canned-file-name-extension)))
129 (expand-file-name name erc-d-u-canned-dialog-dir)))
130
131(defun erc-d-u--massage-canned-name (dialog)
132 "Return DIALOG in a form acceptable to `erc-d-run'."
133 (if (or (symbolp dialog) (file-exists-p dialog))
134 dialog
135 (erc-d-u--expand-dialog-symbol (intern dialog))))
136
137(defun erc-d-u--canned-load-dialog (dialog)
138 "Load dispensing exchanges from DIALOG.
139If DIALOG is a string, consider it a filename. Otherwise find a file
140in `erc-d-u-canned-dialog-dir' with a base name matching the symbol's
141name.
142
143Return an iterator that yields exchanges, each one an iterator of spec
144forms. The first is a so-called request spec and the rest are composed
145of zero or more response specs."
146 (when (symbolp dialog)
147 (setq dialog (erc-d-u--expand-dialog-symbol dialog)))
148 (unless (file-exists-p dialog)
149 (error "File not found: %s" dialog))
150 (erc-d-u--canned-read dialog))
151
152(defun erc-d-u--read-exchange-slowly (num orig info)
153 (when-let ((spec (funcall orig info)))
154 (when (symbolp (car spec))
155 (setf spec (copy-sequence spec)
156 (nth 1 spec) (cond ((functionp num) (funcall num (nth 1 spec)))
157 ((< num 0) (max (nth 1 spec) (- num)))
158 (t (+ (nth 1 spec) num)))))
159 spec))
160
161(defun erc-d-u--rewrite-for-slow-mo (num read-info)
162 "Return READ-INFO with a modified reader.
163When NUM is a positive number, delay incoming requests by NUM more
164seconds. If NUM is negative, raise insufficient incoming delays to at
165least -NUM seconds. If NUM is a function, set each delay to whatever it
166returns when called with the existing value."
167 (let ((orig (erc-d-u-scan-d-f read-info)))
168 (setf (erc-d-u-scan-d-f read-info)
169 (apply-partially #'erc-d-u--read-exchange-slowly num orig))
170 read-info))
171
172(defun erc-d-u--get-remote-port (process)
173 "Return peer TCP port for client PROCESS.
174When absent, just generate an id."
175 (let ((remote (plist-get (process-contact process t) :remote)))
176 (if (vectorp remote)
177 (aref remote (1- (length remote)))
178 (format "%s:%d" (process-contact process :local)
179 (logand 1023 (time-convert nil 'integer))))))
180
181(defun erc-d-u--format-bind-address (process)
182 "Return string or (STRING . INT) for bind address of network PROCESS."
183 (let ((local (process-contact process :local)))
184 (if (vectorp local) ; inet
185 (cons (mapconcat #'number-to-string (seq-subseq local 0 -1) ".")
186 (aref local (1- (length local))))
187 local)))
188
189(defun erc-d-u--unkeyword (plist)
190 "Return a copy of PLIST with keywords keys converted to non-keywords."
191 (cl-loop for (key value) on plist by #'cddr
192 when (keywordp key)
193 do (setq key (intern (substring (symbol-name key) 1)))
194 append (list key value)))
195
196(defun erc-d-u--massage-rx-args (key val)
197 " Massage val so it's suitable for an `rx-let' binding.
198Handle cases in which VAL is ([ARGLIST] RX-FORM) rather than just
199RX-FORM. KEY becomes the binding name."
200 (if (and (listp val)
201 (cdr val)
202 (not (cddr val))
203 (consp (car val)))
204 (cons key val)
205 (list key val)))
206
207(defvar-local erc-d-u--process-buffer nil
208 "Beacon for erc-d process buffers.
209The server process is usually deleted first, but we may want to examine
210the buffer afterward.")
211
212(provide 'erc-d-u)
213;;; erc-d-u.el ends here
diff --git a/test/lisp/erc/resources/erc-d/erc-d.el b/test/lisp/erc/resources/erc-d/erc-d.el
new file mode 100644
index 00000000000..ee9b6a7fec9
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/erc-d.el
@@ -0,0 +1,997 @@
1;;; erc-d.el --- A dumb test server for ERC -*- lexical-binding: t -*-
2
3;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
4;;
5;; This file is part of GNU Emacs.
6;;
7;; This program is free software: you can redistribute it and/or
8;; modify it under the terms of the GNU General Public License as
9;; published by the Free Software Foundation, either version 3 of the
10;; License, or (at your option) any later version.
11;;
12;; This program is distributed in the hope that it will be useful, but
13;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15;; General Public License for more details.
16;;
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see
19;; <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;; This is a netcat style server for testing ERC. The "d" in the name
24;; stands for "daemon" as well as for "dialog" (as well as for "dumb"
25;; because this server isn't very smart). It either spits out a
26;; canned reply when an incoming request matches the expected regexp
27;; or signals an error and dies. The entry point function is
28;; `erc-d-run'.
29;;
30;; Canned scripts, or "dialogs," should be Lisp-Data files containing
31;; one or more request/reply forms like this:
32;;
33;; | ((mode-chan 1.5 "MODE #chan") ; request: tag, expr, regex
34;; | (0.1 ":irc.org 324 bob #chan +Cint") ; reply: delay, content
35;; | (0.0 ":irc.org 329 bob #chan 12345")) ; reply: ...
36;;
37;; These are referred to as "exchanges." The first element is a list
38;; whose CAR is a descriptive "tag" and whose CDR is an incoming
39;; "spec" representing an inbound message from the client. The rest
40;; of the exchange is composed of outgoing specs representing
41;; server-to-client messages. A tag can be any symbol (ideally unique
42;; in the dialog), but a leading tilde means the request should be
43;; allowed to arrive out of order (within the allotted time).
44;;
45;; The first element in an incoming spec is a number indicating the
46;; maximum number of seconds to wait for a match before raising an
47;; error. The CDR is interpreted as the collective arguments of an
48;; `rx' form to be matched against the raw request (stripped of its
49;; CRLF line ending). A "string-start" backslash assertion, "\\`", is
50;; prepended to all patterns.
51;;
52;; Similarly, the leading number in an *outgoing* spec indicates how
53;; many seconds to wait before sending the line, which is rendered by
54;; concatenating the other members after evaluating each in place.
55;; CRLF line endings are appended on the way out and should be absent.
56;;
57;; Recall that IRC is "asynchronous," meaning some flow intervals
58;; don't jibe with lockstep request-reply semantics. However, for our
59;; purposes, grouping things as [input, output1, ..., outputN] makes
60;; sense, even though input and output may be completely unrelated.
61;;
62;; Template interpolation:
63;;
64;; A rudimentary templating facility is provided for additional
65;; flexibility. However, it's best to keep things simple (even if
66;; overly verbose), so others can easily tell what's going on at a
67;; glance. If necessary, consult existing tests for examples (grep
68;; for the variables `erc-d-tmpl-vars' and `erc-d-match-handlers').
69;;
70;; Subprocess or in-process?:
71;;
72;; Running in-process confers better visibility and easier setup at
73;; the cost of additional cleanup and resource wrangling. With a
74;; subprocess, cleanup happens by pulling the plug, but configuration
75;; means loading a separate file or passing -eval "(forms...)" during
76;; invocation. In some cases, a subprocess may be the only option,
77;; like when trying to avoid `require'ing this file.
78;;
79;; Dialog objects:
80;;
81;; For a given exchange, the first argument passed to a request
82;; handler is the `erc-d-dialog' object representing the overall
83;; conversation with the connecting peer. It can be used to pass
84;; information between handlers during a session. Some important
85;; items are:
86;;
87;; * name (symbol); name of the current dialog
88;;
89;; * queue (ring); a backlog of unhandled raw requests, minus CRLF
90;; endings.
91;;
92;; * timers (list of timers); when run, these send messages originally
93;; deferred as per the most recently matched exchange's delay info.
94;; Normally, all outgoing messages must be sent before another request
95;; is considered. (See `erc-d--send-outgoing' for an escape hatch.)
96;;
97;; * hunks (iterator of iterators); unconsumed exchanges as read from
98;; a Lisp-Data dialog file. The exchange iterators being dispensed
99;; themselves yield portions of member forms as a 2- or 3-part
100;; sequence: [tag] spec. (Here, "hunk" just means "list of raw,
101;; unrendered exchange elements")
102;;
103;; * vars (alist of cons pairs); for sharing state among template
104;; functions during the lifetime of an exchange. Initially populated
105;; by `erc-d-tmpl-vars', these KEY/VALUE pairs are expanded in the
106;; templates and optionally updated by "exchange handlers" (see
107;; `erc-d-match-handlers'). When VALUE is a function, occurrences of
108;; KEY in an outgoing spec are replaced with the result of calling
109;; VALUE with match data set appropriately. See
110;; `erc-d--render-entries' for details.
111;;
112;; * exchanges (ring of erc-d-exchange objects); activated hunks
113;; allowed to match out of order, plus the current active exchange
114;; being yielded from, if any. See `erc-d-exchange'.
115;;
116;; TODO
117;;
118;; - Remove un(der)used functionality and simplify API
119;; - Maybe migrate d-u and d-i dependencies here
120
121;;; Code:
122(eval-and-compile
123 (let* ((d (file-name-directory (or (macroexp-file-name) buffer-file-name)))
124 (load-path (cons (directory-file-name d) load-path)))
125 (require 'erc-d-i)
126 (require 'erc-d-u)))
127
128(require 'ring)
129
130(defvar erc-d-server-name "erc-d-server"
131 "Default name of a server process and basis for its buffer name.
132Only relevant when starting a server with `erc-d-run'.")
133
134(defvar erc-d-server-fqdn "irc.example.org"
135 "Usually the same as the server's RPL_MYINFO \"announced name\".
136Possibly used by overriding handlers, like the one for PING, and/or
137dialog templates for the sender portion of a reply message.")
138
139(defvar erc-d-linger-secs nil
140 "Seconds to wait before quitting for all dialogs.
141For more granular control, use the provided LINGER `rx' variable (alone)
142as the incoming template spec of a dialog's last exchange.")
143
144(defvar erc-d-tmpl-vars nil
145 "An alist of template bindings available to client dialogs.
146Populate it when calling `erc-d-run', and the contents will be made
147available to all client dialogs through the `erc-d-dialog' \"vars\"
148field and (therefore) to all templates as variables when rendering. For
149example, a key/value pair like (network . \"oftc\") will cause instances
150of the (unquoted) symbol `network' to be replaced with \"oftc\" in the
151rendered template string.
152
153This list provides default template bindings common to all dialogs.
154Each new client-connection process makes a shallow copy on init, but the
155usual precautions apply when mutating member items. Within the span of
156a dialog, updates not applicable to all exchanges should die with their
157exchange. See `erc-d--render-entries' for details. In the unlikely
158event that an exchange-specific handler is needed, see
159`erc-d-match-handlers'.")
160
161(defvar erc-d-match-handlers nil
162 "A plist of exchange-tag symbols mapped to request-handler functions.
163This is meant to address edge cases for which `erc-d-tmpl-vars' comes up
164short. These may include (1) needing access to the client process
165itself and/or (2) adding or altering outgoing response templates before
166rendering. Note that (2) requires using `erc-d-exchange-rebind' instead
167of manipulating exchange bindings directly.
168
169The hook-like function `erc-d-on-match' calls any handler whose key is
170`eq' to the tag of the currently matched exchange (passing the client
171`erc-d-dialog' as the first argument and the current `erc-d-exchange'
172object as the second). The handler runs just prior to sending the first
173response.")
174
175(defvar erc-d-auto-pong t
176 "Handle PING requests automatically.")
177
178(defvar erc-d--in-process t
179 "Whether the server is running in the same Emacs as ERT.")
180
181(defvar erc-d--slow-mo nil
182 "Adjustment for all incoming timeouts.
183This is to allow for human interaction or a slow Emacs or CI runner.
184The value is the number of seconds to extend all incoming spec timeouts
185by on init. If the value is a negative number, it's negated and
186interpreted as a lower bound to raise all incoming timeouts to. If the
187value is a function, it should take an existing timeout in seconds and
188return a replacement.")
189
190(defconst erc-d--eof-sentinel "__EOF__")
191(defconst erc-d--linger-sentinel "__LINGER__")
192(defconst erc-d--drop-sentinel "__DROP__")
193
194(defvar erc-d--clients nil
195 "List containing all clients for this server session.")
196
197;; Some :type names may just be made up (not actual CL types)
198
199(cl-defstruct (erc-d-spec) ; see `erc-d--render-entries'
200 (head nil :type symbol) ; or number?
201 (entry nil :type list)
202 (state 0 :type integer))
203
204(cl-defstruct (erc-d-exchange)
205 "Object representing a request/response unit from a canned dialog."
206 (dialog nil :type erc-d-dialog) ; owning dialog
207 (tag nil :type symbol) ; a.k.a. tag, the caar
208 (pattern nil :type string) ; regexp to match requests against
209 (inspec nil :type list) ; original unrendered incoming spec
210 (hunk nil :type erc-d-u-scan-e) ; active raw exchange hunk being yielded
211 (spec nil :type erc-d-spec) ; active spec, see `erc-d--render-entries'
212 (timeout nil :type number) ; time allotted for current request
213 (timer nil :type timer) ; match timer fires when timeout expires
214 (bindings nil :type list) ; `eval'-style env pairs (KEY . VAL) ...
215 (rx-bindings nil :type list) ; rx-let bindings
216 (deferred nil :type boolean) ; whether sender is paused
217 ;; Post-match
218 (match-data nil :type match-data) ; from the latest matched request
219 (request nil :type string)) ; the original request sans CRLF
220
221(cl-defstruct (erc-d-dialog)
222 "Session state for managing a client conversation."
223 (process nil :type process) ; client-connection process
224 (name nil :type symbol) ; likely the interned stem of the file
225 (queue nil :type ring) ; backlog of incoming lines to process
226 (hunks nil :type erc-d-u-scan-d) ; nil when done; info on raw exchange hunks
227 (timers nil :type list) ; unsent replies
228 (vars nil :type list) ; template bindings for rendering
229 (exchanges nil :type ring) ; ring of erc-d-exchange objects
230 (state nil :type symbol) ; handler's last recorded control state
231 (matched nil :type erc-d-exchange) ; currently matched exchange
232 (message nil :type erc-d-i-message) ; `erc-d-i-message'
233 (match-handlers nil :type list) ; copy of `erc-d-match-handlers'
234 (server-fqdn nil :type string) ; copy of `erc-d-server-fqdn'
235 (finalizer nil :type function) ; custom teardown, passed dialog and exchange
236 ;; Post-match history is a plist whose keys are exchange tags
237 ;; (symbols) and whose values are a cons of match-data and request
238 ;; values from prior matches.
239 (history nil :type list))
240
241(defun erc-d--initialize-client (process)
242 "Initialize state variables used by a client PROCESS."
243 ;; Discard server-only/owned props
244 (process-put process :dialog-dialogs nil)
245 (let* ((server (process-get process :server))
246 (reader (pop (process-get server :dialog-dialogs)))
247 (name (pop reader))
248 ;; Copy handlers so they can self-mutate per process
249 (mat-h (copy-sequence (process-get process :dialog-match-handlers)))
250 (fqdn (copy-sequence (process-get process :dialog-server-fqdn)))
251 (vars (copy-sequence (process-get process :dialog-vars)))
252 (dialog (make-erc-d-dialog :name name
253 :process process
254 :queue (make-ring 5)
255 :exchanges (make-ring 10)
256 :match-handlers mat-h
257 :server-fqdn fqdn)))
258 ;; Add items expected by convenience commands like `erc-d-exchange-reload'.
259 (setf (alist-get 'EOF vars) `(: ,erc-d--eof-sentinel eot)
260 (alist-get 'LINGER vars) `(: ,erc-d--linger-sentinel eot)
261 (alist-get 'DROP vars) `(: ,erc-d--drop-sentinel eot)
262 (erc-d-dialog-vars dialog) vars
263 (erc-d-dialog-hunks dialog) reader)
264 ;; Add reverse link, register client, launch
265 (process-put process :dialog dialog)
266 (push process erc-d--clients)
267 (erc-d--command-refresh dialog nil)
268 (erc-d--on-request process)))
269
270(defun erc-d-load-replacement-dialog (dialog replacement &optional skip)
271 "Find REPLACEMENT among backlog and swap out current DIALOG's iterator.
272With int SKIP, advance past that many exchanges."
273 (let* ((process (erc-d-dialog-process dialog))
274 (server (process-get process :server))
275 (reader (assoc-default replacement
276 (process-get server :dialog-dialogs)
277 #'eq)))
278 (when skip (while (not (zerop skip))
279 (erc-d-u--read-dialog reader)
280 (cl-decf skip)))
281 (dolist (timer (erc-d-dialog-timers dialog))
282 (cancel-timer timer))
283 (dolist (exchange (ring-elements (erc-d-dialog-exchanges dialog)))
284 (cancel-timer (erc-d-exchange-timer exchange)))
285 (setf (erc-d-dialog-hunks dialog) reader)
286 (erc-d--command-refresh dialog nil)))
287
288(defvar erc-d--m-debug (getenv "ERC_D_DEBUG"))
289
290(defmacro erc-d--m (process format-string &rest args)
291 "Output ARGS using FORMAT-STRING somewhere depending on context.
292PROCESS should be a client connection or a server network process."
293 `(let ((format-string (if erc-d--m-debug
294 (concat (format-time-string "%s.%N: ")
295 ,format-string)
296 ,format-string))
297 (want-insert (and ,process erc-d--in-process)))
298 (when want-insert
299 (with-current-buffer (process-buffer (process-get ,process :server))
300 (goto-char (point-max))
301 (insert (concat (format ,format-string ,@args) "\n"))))
302 (when (or erc-d--m-debug (not want-insert))
303 (message format-string ,@args))))
304
305(defmacro erc-d--log (process string &optional outbound)
306 "Log STRING sent to (OUTBOUND) or received from PROCESS peer."
307 `(let ((id (or (process-get ,process :log-id)
308 (let ((port (erc-d-u--get-remote-port ,process)))
309 (process-put ,process :log-id port)
310 port)))
311 (name (erc-d-dialog-name (process-get ,process :dialog))))
312 (if ,outbound
313 (erc-d--m process "-> %s:%s %s" name id ,string)
314 (dolist (line (split-string ,string "\r\n"))
315 (erc-d--m process "<- %s:%s %s" name id line)))))
316
317(defun erc-d--log-process-event (server process msg)
318 (erc-d--m server "%s: %s" process (string-trim-right msg)))
319
320(defun erc-d--send (process string)
321 "Send STRING to PROCESS peer."
322 (erc-d--log process string 'outbound)
323 (process-send-string process (concat string "\r\n")))
324
325(define-inline erc-d--fuzzy-p (exchange)
326 (inline-letevals (exchange)
327 (inline-quote
328 (let ((tag (symbol-name (erc-d-exchange-tag ,exchange))))
329 (eq ?~ (aref tag 0))))))
330
331(define-error 'erc-d-timeout "Timed out awaiting expected request")
332
333(defun erc-d--finalize-dialog (dialog)
334 "Delete client-connection and finalize DIALOG.
335Return associated server."
336 (let ((process (erc-d-dialog-process dialog)))
337 (setq erc-d--clients (delq process erc-d--clients))
338 (dolist (timer (erc-d-dialog-timers dialog))
339 (cancel-timer timer))
340 (dolist (exchange (ring-elements (erc-d-dialog-exchanges dialog)))
341 (cancel-timer (erc-d-exchange-timer exchange)))
342 (prog1 (process-get process :server)
343 (delete-process process))))
344
345(defun erc-d--teardown (&optional sig &rest msg)
346 "Clean up processes and maybe send signal SIG using MSG."
347 (unless erc-d--in-process
348 (when sig
349 (erc-d--m nil "%s %s" sig (apply #'format-message msg)))
350 (kill-emacs (if msg 1 0)))
351 (let (process servers)
352 (while (setq process (pop erc-d--clients))
353 (push (erc-d--finalize-dialog (process-get process :dialog)) servers))
354 (dolist (server servers)
355 (delete-process server)))
356 (dolist (timer timer-list)
357 (when (memq (timer--function timer)
358 '(erc-d--send erc-d--command-handle-all))
359 (erc-d--m nil "Stray timer found: %S" (timer--function timer))
360 (cancel-timer timer)))
361 (when sig
362 (dolist (buf erc-d-u--canned-buffers)
363 (kill-buffer buf))
364 (setq erc-d-u--canned-buffers nil)
365 (signal sig (list (apply #'format-message msg)))))
366
367(defun erc-d--teardown-this-dialog-at-least (dialog)
368 "Run `erc-d--teardown' after destroying DIALOG if it's the last one."
369 (let ((server (process-get (erc-d-dialog-process dialog) :server))
370 (us (erc-d-dialog-process dialog)))
371 (erc-d--finalize-dialog dialog)
372 (cl-assert (not (memq us erc-d--clients)))
373 (unless (or (process-get server :dialog-dialogs)
374 (catch 'other
375 (dolist (process erc-d--clients)
376 (when (eq (process-get process :server) server)
377 (throw 'other process)))))
378 (push us erc-d--clients)
379 (erc-d--teardown))))
380
381(defun erc-d--expire (dialog exchange)
382 "Raise timeout error for EXCHANGE.
383This will start the teardown for DIALOG."
384 (setf (erc-d-exchange-spec exchange) nil)
385 (if-let ((finalizer (erc-d-dialog-finalizer dialog)))
386 (funcall finalizer dialog exchange)
387 (erc-d--teardown 'erc-d-timeout "Timed out awaiting request: %s"
388 (list :name (erc-d-exchange-tag exchange)
389 :pattern (erc-d-exchange-pattern exchange)
390 :timeout (erc-d-exchange-timeout exchange)
391 :dialog (erc-d-dialog-name dialog)))))
392
393;; Using `run-at-time' here allows test cases to examine replies as
394;; they arrive instead of forcing tests to wait until an exchange
395;; completes. The `run-at-time' in `erc-d--command-meter-replies'
396;; does the same. When running as a subprocess, a normal while loop
397;; with a `sleep-for' works fine (including with multiple dialogs).
398;; FYI, this issue was still present in older versions that called
399;; this directly from `erc-d--filter'.
400
401(defun erc-d--on-request (process)
402 "Handle one request for client-connection PROCESS."
403 (when (process-live-p process)
404 (let* ((dialog (process-get process :dialog))
405 (queue (erc-d-dialog-queue dialog)))
406 (unless (ring-empty-p queue)
407 (let* ((parsed (ring-remove queue))
408 (cmd (intern (erc-d-i-message.command parsed))))
409 (setf (erc-d-dialog-message dialog) parsed)
410 (erc-d-command dialog cmd)))
411 (run-at-time nil nil #'erc-d--on-request process))))
412
413(defun erc-d--drop-p (exchange)
414 (memq 'DROP (erc-d-exchange-inspec exchange)))
415
416(defun erc-d--linger-p (exchange)
417 (memq 'LINGER (erc-d-exchange-inspec exchange)))
418
419(defun erc-d--fake-eof (dialog)
420 "Simulate receiving a fictitious \"EOF\" message from peer."
421 (setf (erc-d-dialog-message dialog) ; use downcase for internal cmds
422 (make-erc-d-i-message :command "eof" :unparsed erc-d--eof-sentinel))
423 (run-at-time nil nil #'erc-d-command dialog 'eof))
424
425(defun erc-d--process-sentinel (process event)
426 "Set up or tear down client-connection PROCESS depending on EVENT."
427 (erc-d--log-process-event process process event)
428 (if (eq 'open (process-status process))
429 (erc-d--initialize-client process)
430 (let* ((dialog (process-get process :dialog))
431 (exes (and dialog (erc-d-dialog-exchanges dialog))))
432 (if (and exes (not (ring-empty-p exes)))
433 (cond ((string-prefix-p "connection broken" event)
434 (erc-d--fake-eof dialog))
435 ;; Ignore disconnecting peer when pattern is DROP
436 ((and (string-prefix-p "deleted" event)
437 (erc-d--drop-p (ring-ref exes -1))))
438 (t (erc-d--teardown)))
439 (erc-d--teardown)))))
440
441(defun erc-d--filter (process string)
442 "Handle input received from peer.
443PROCESS represents a client peer connection and STRING is a raw request
444including line delimiters."
445 (let ((queue (erc-d-dialog-queue (process-get process :dialog))))
446 (setq string (concat (process-get process :stashed-input) string))
447 (while (and string (string-match (rx (+ "\r\n")) string))
448 (let ((line (substring string 0 (match-beginning 0))))
449 (setq string (unless (= (match-end 0) (length string))
450 (substring string (match-end 0))))
451 (erc-d--log process line nil)
452 (ring-insert queue (erc-d-i--parse-message line 'decode))))
453 (when string
454 (setf (process-get process :stashed-input) string))))
455
456;; Misc process properties:
457;;
458;; The server property `:dialog-dialogs' is an alist of (symbol
459;; . erc-d-u-scan-d) conses, each of which pairs a dialogs name with
460;; info on its read progress (described above in the Commentary).
461;; This list is populated by `erc-d-run' at the start of each session.
462;;
463;; Client-connection processes keep a reference to their server via a
464;; `:server' property, which can be used to share info with other
465;; clients. There is currently no built-in way to do the same with
466;; clients of other servers. Clients also keep references to their
467;; dialogs and raw messages via `:dialog' and `:stashed-input'.
468;;
469;; The logger stores a unique, human-friendly process name in the
470;; client-process property `:log-id'.
471
472(defun erc-d--start (host service name &rest plist)
473 "Serve canned replies on HOST at SERVICE.
474Return the new server process immediately when `erc-d--in-process' is
475non-nil. Otherwise, serve forever. PLIST becomes the plist of the
476server process and is used to initialize the plists of connection
477processes. NAME is used for the process and the buffer."
478 (let* ((buf (get-buffer-create (concat "*" name "*")))
479 (proc (make-network-process :server t
480 :buffer buf
481 :noquery t
482 :filter #'erc-d--filter
483 :log #'erc-d--log-process-event
484 :sentinel #'erc-d--process-sentinel
485 :name name
486 :family (if host 'ipv4 'local)
487 :coding 'binary
488 :service (or service t)
489 :host host
490 :plist plist)))
491 (process-put proc :server proc)
492 ;; We don't have a minor mode, so use an arbitrary variable to mark
493 ;; buffers owned by us instead
494 (with-current-buffer buf (setq erc-d-u--process-buffer t))
495 (erc-d--m proc "Starting network process: %S %S"
496 proc (erc-d-u--format-bind-address proc))
497 (if erc-d--in-process
498 proc
499 (while (process-live-p proc)
500 (accept-process-output nil 0.01)))))
501
502(defun erc-d--wrap-func-val (dialog exchange key func)
503 "Return a form invoking FUNC when evaluated.
504Arrange for FUNC to be called with the args it expects based on
505the description in `erc-d--render-entries'."
506 (let (args)
507 ;; Ignore &rest or &optional
508 (pcase-let ((`(,n . ,_) (func-arity func)))
509 (pcase n
510 (0)
511 (1 (push (apply-partially #'erc-d-exchange-multi dialog exchange key)
512 args))
513 (2 (push exchange args)
514 (push (apply-partially #'erc-d-exchange-multi dialog exchange key)
515 args))
516 (_ (error "Incompatible function: %s" func))))
517 (lambda () (apply func args))))
518
519(defun erc-d-exchange-reload (dialog exchange)
520 "Rebuild all bindings for EXCHANGE from those in DIALOG."
521 (cl-loop for (key . val) in (erc-d-dialog-vars dialog)
522 unless (keywordp key)
523 do (push (erc-d-u--massage-rx-args key val)
524 (erc-d-exchange-rx-bindings exchange))
525 when (functionp val) do
526 (setq val (erc-d--wrap-func-val dialog exchange key val))
527 do (push (cons key val) (erc-d-exchange-bindings exchange))))
528
529(defun erc-d-exchange-rebind (dialog exchange key val &optional export)
530 "Modify a binding between renders.
531
532Bind symbol KEY to VAL, replacing whatever existed before, which may
533have been a function. A third, optional argument, if present and
534non-nil, results in the DIALOG's bindings for all EXCHANGEs adopting
535this binding. VAL can either be a function of the type described in
536`erc-d--render-entries' or any value acceptable as an argument to the
537function `concat'.
538
539DIALOG and EXCHANGE are the current `erc-d-dialog' and `erc-d-exchange'
540objects for the request context."
541 (when export
542 (setf (alist-get key (erc-d-dialog-vars dialog)) val))
543 (if (functionp val)
544 (setf (alist-get key (erc-d-exchange-bindings exchange))
545 (erc-d--wrap-func-val dialog exchange key val))
546 (setf (alist-get key (erc-d-exchange-rx-bindings exchange)) (list val)
547 (alist-get key (erc-d-exchange-bindings exchange)) val))
548 val)
549
550(defun erc-d-exchange-match (exchange match-number &optional tag)
551 "Return match portion of current or previous request.
552MATCH-NUMBER is the match group number. TAG, if provided, means the
553exchange tag (name) from some previously matched request."
554 (if tag
555 (pcase-let* ((dialog (erc-d-exchange-dialog exchange))
556 (`(,m-d . ,req) (plist-get (erc-d-dialog-history dialog)
557 tag)))
558 (set-match-data m-d)
559 (match-string match-number req))
560 (match-string match-number (erc-d-exchange-request exchange))))
561
562(defun erc-d-exchange-multi (dialog exchange key cmd &rest args)
563 "Call CMD with ARGS.
564This is a utility passed as the first argument to all template
565functions. DIALOG and EXCHANGE are pre-applied. A few pseudo
566commands, like `:request', are provided for convenience so that
567the caller's definition doesn't have to include this file. The
568rest are access and mutation utilities, such as `:set', which
569assigns KEY a new value, `:get-binding', which looks up KEY in
570`erc-d-exchange-bindings', and `:get-var', which looks up KEY in
571`erc-d-dialog-vars'."
572 (pcase cmd
573 (:set (apply #'erc-d-exchange-rebind dialog exchange key args))
574 (:reload (apply #'erc-d-exchange-reload dialog exchange args))
575 (:rebind (apply #'erc-d-exchange-rebind dialog exchange args))
576 (:match (apply #'erc-d-exchange-match exchange args))
577 (:request (erc-d-exchange-request exchange))
578 (:match-data (erc-d-exchange-match-data exchange))
579 (:dialog-name (erc-d-dialog-name dialog))
580 (:get-binding (cdr (assq (car args) (erc-d-exchange-bindings exchange))))
581 (:get-var (alist-get (car args) (erc-d-dialog-vars dialog)))))
582
583(defun erc-d--render-incoming-entry (exchange spec)
584 (let ((rx--local-definitions (rx--extend-local-defs
585 (erc-d-exchange-rx-bindings exchange))))
586 (rx-to-string `(: bos ,@(erc-d-spec-entry spec)) 'no-group)))
587
588(defun erc-d--render-outgoing-entry (exchange entry)
589 (let (out this)
590 (while (setq this (pop entry))
591 (set-match-data (erc-d-exchange-match-data exchange))
592 (unless (stringp this)
593 (cl-assert (symbolp this))
594 (setq this (or (alist-get this (erc-d-exchange-bindings exchange))
595 (symbol-value this)))
596 ;; Allow reference to overlong var name unbecoming of a template
597 (when this
598 (when (symbolp this) (setq this (symbol-value this)))
599 (when (functionp this) (setq this (save-match-data (funcall this))))
600 (unless (stringp this) (error "Unexpected token %S" this))))
601 (push this out))
602 (apply #'concat (nreverse out))))
603
604(defun erc-d--render-entries (exchange &optional yield-result)
605 "Act as an iterator producing rendered strings from EXCHANGE hunks.
606When an entry's CAR is an arbitrary symbol, yield that back first, and
607consider the entry an \"incoming\" entry. Then, regardless of the
608entry's type (incoming or outgoing), yield back the next element, which
609should be a number representing either a timeout (incoming) or a
610delay (outgoing). After that, yield a rendered template (outgoing) or a
611regular expression (incoming); both should be treated as immutable.
612
613When evaluating a template, bind the keys in the alist stored in the
614dialog's `vars' field to its values, but skip any self-quoters, like
615:foo. When an entry is incoming, replace occurrences of a key with its
616value, which can be any valid `rx' form (see Info node `(elisp)
617Extending Rx'). Do the same when an entry is outgoing, but expect a
618value's form to be (anything that evaluates to) something acceptable by
619`concat' or, alternatively, a function that returns a string or nil.
620
621Repeat the last two steps for the remaining entries, all of which are
622assumed to be outgoing. That is, continue yielding a timeout/delay and
623a rendered string for each entry, and yield nil when exhausted.
624
625Once again, for an incoming entry, the yielded string is a regexp to be
626matched against the raw request. For outgoing, it's the final response,
627ready to be sent out (after adding the appropriate line ending).
628
629To help with testing, bindings are not automatically created from
630DIALOG's \"vars\" alist when this function is invoked. But this can be
631forced by sending a non-nil YIELD-RESULT into the generator on the
632second \"next\" invocation of a given iteration. This clobbers any
633temporary bindings that don't exist in the DIALOG's `vars' alist, such
634as those added via `erc-d-exchange-rebind' (unless \"exported\").
635
636As noted earlier, template symbols can be bound to functions. When
637called during rendering, the match data from the current (matched)
638request is accessible by calling the function `match-data'.
639
640A function may ask for up to two required args, which are provided as
641needed. When applicable, the first required arg is a `funcall'-able
642helper that accepts various keyword-based commands, like :rebind, and a
643variable number of args. See `erc-d-exchange-multi' for details. When
644specified, the second required arg is the current `erc-d-exchange'
645object, which has among its members its owning `erc-d-dialog' object.
646This should suffice as a safety valve for any corner-case needs.
647Non-required args are ignored."
648 (let ((spec (erc-d-exchange-spec exchange))
649 (dialog (erc-d-exchange-dialog exchange))
650 (entries (erc-d-exchange-hunk exchange)))
651 (unless (erc-d-spec-entry spec)
652 (setf (erc-d-spec-entry spec) (erc-d-u--read-exchange entries)))
653 (catch 'yield
654 (while (erc-d-spec-entry spec)
655 (pcase (erc-d-spec-state spec)
656 (0 (cl-incf (erc-d-spec-state spec))
657 (throw 'yield (setf (erc-d-spec-head spec)
658 (pop (erc-d-spec-entry spec)))))
659 (1 (cl-incf (erc-d-spec-state spec))
660 (when yield-result
661 (erc-d-exchange-reload dialog exchange))
662 (unless (numberp (erc-d-spec-head spec))
663 (setf (erc-d-exchange-inspec exchange) (erc-d-spec-entry spec))
664 (throw 'yield
665 (prog1 (pop (erc-d-spec-entry spec))
666 (setf (erc-d-spec-entry spec)
667 (erc-d--render-incoming-entry exchange spec))))))
668 (2 (setf (erc-d-spec-state spec) 0)
669 (throw 'yield
670 (let ((entry (erc-d-spec-entry spec)))
671 (setf (erc-d-spec-entry spec) nil)
672 (if (stringp entry)
673 entry
674 (erc-d--render-outgoing-entry exchange entry))))))))))
675
676(defun erc-d--iter (exchange)
677 (apply-partially #'erc-d--render-entries exchange))
678
679(defun erc-d-on-match (dialog exchange)
680 "Handle matched exchange request.
681Allow the first handler in `erc-d-match-handlers' whose key matches TAG
682to manipulate replies before they're sent to the DIALOG peer."
683 (when-let* ((tag (erc-d-exchange-tag exchange))
684 (handler (plist-get (erc-d-dialog-match-handlers dialog) tag)))
685 (let ((md (erc-d-exchange-match-data exchange)))
686 (set-match-data md)
687 (funcall handler dialog exchange))))
688
689(defun erc-d--send-outgoing (dialog exchange)
690 "Send outgoing lines for EXCHANGE to DIALOG peer.
691Assume the next spec is outgoing. If its delay value is zero, render
692the template and send the resulting message straight away. Do the same
693when DELAY is negative, only arrange for its message to be sent (abs
694DELAY) seconds later, and then keep on processing. If DELAY is
695positive, pause processing and yield DELAY."
696 (let ((specs (erc-d--iter exchange))
697 (process (erc-d-dialog-process dialog))
698 (deferred (erc-d-exchange-deferred exchange))
699 delay)
700 ;; Could stash/pass thunk instead to ensure specs can't be mutated
701 ;; between calls (by temporarily replacing dialog member with a fugazi)
702 (when deferred
703 (erc-d--send process (funcall specs))
704 (setf deferred nil (erc-d-exchange-deferred exchange) deferred))
705 (while (and (not deferred) (setq delay (funcall specs)))
706 (cond ((zerop delay) (erc-d--send process (funcall specs)))
707 ((< delay 0) (push (run-at-time (- delay) nil #'erc-d--send
708 process (funcall specs))
709 (erc-d-dialog-timers dialog)))
710 ((setf deferred t (erc-d-exchange-deferred exchange) deferred))))
711 delay))
712
713(defun erc-d--add-dialog-linger (dialog exchange)
714 "Add finalizer for EXCHANGE in DIALOG."
715 (erc-d--m (erc-d-dialog-process dialog)
716 "Lingering for %.2f seconds" (erc-d-exchange-timeout exchange))
717 (let ((start (current-time)))
718 (setf (erc-d-dialog-finalizer dialog)
719 (lambda (&rest _)
720 (erc-d--m (erc-d-dialog-process dialog)
721 "Lingered for %.2f seconds"
722 (float-time (time-subtract (current-time) start)))
723 (erc-d--teardown-this-dialog-at-least dialog)))))
724
725(defun erc-d--add-dialog-drop (dialog exchange)
726 "Add finalizer for EXCHANGE in DIALOG."
727 (erc-d--m (erc-d-dialog-process dialog)
728 "Dropping in %.2f seconds" (erc-d-exchange-timeout exchange))
729 (setf (erc-d-dialog-finalizer dialog)
730 (lambda (&rest _)
731 (erc-d--m (erc-d-dialog-process dialog)
732 "Dropping %S" (erc-d-dialog-name dialog))
733 (erc-d--finalize-dialog dialog))))
734
735(defun erc-d--create-exchange (dialog hunk)
736 "Initialize next exchange HUNK for DIALOG."
737 (let* ((spec (make-erc-d-spec))
738 (exchange (make-erc-d-exchange :dialog dialog :hunk hunk :spec spec))
739 (specs (erc-d--iter exchange)))
740 (setf (erc-d-exchange-tag exchange) (funcall specs)
741 (erc-d-exchange-timeout exchange) (funcall specs t)
742 (erc-d-exchange-pattern exchange) (funcall specs))
743 (cond ((erc-d--linger-p exchange)
744 (erc-d--add-dialog-linger dialog exchange))
745 ((erc-d--drop-p exchange)
746 (erc-d--add-dialog-drop dialog exchange)))
747 (setf (erc-d-exchange-timer exchange)
748 (run-at-time (erc-d-exchange-timeout exchange)
749 nil #'erc-d--expire dialog exchange))
750 exchange))
751
752(defun erc-d--command-consider-prep-fail (dialog line exes)
753 (list 'error "Match failed: %S %S" line
754 (list :exes (mapcar #'erc-d-exchange-pattern
755 (ring-elements exes))
756 :dialog (erc-d-dialog-name dialog))))
757
758(defun erc-d--command-consider-prep-success (dialog line exes matched)
759 (setf (erc-d-exchange-request matched) line
760 (erc-d-exchange-match-data matched) (match-data)
761 ;; Also add current to match history, indexed by exchange tag
762 (plist-get (erc-d-dialog-history dialog)
763 (erc-d-exchange-tag matched))
764 (cons (match-data) line)) ; do we need to make a copy of this?
765 (cancel-timer (erc-d-exchange-timer matched))
766 (ring-remove exes (ring-member exes matched)))
767
768(cl-defun erc-d--command-consider (dialog)
769 "Maybe return next matched exchange for DIALOG.
770Upon encountering a mismatch, return an error of the form (ERROR-SYMBOL
771DATA). But when only fuzzies remain in the exchange pool, return nil."
772 (let* ((parsed (erc-d-dialog-message dialog))
773 (line (erc-d-i-message.unparsed parsed))
774 (exes (erc-d-dialog-exchanges dialog))
775 ;;
776 matched)
777 (let ((elts (ring-elements exes)))
778 (while (and (setq matched (pop elts))
779 (not (string-match (erc-d-exchange-pattern matched) line)))
780 (if (and (not elts) (erc-d--fuzzy-p matched))
781 ;; Nothing to do, so advance
782 (cl-return-from erc-d--command-consider nil)
783 (cl-assert (or (not elts) (erc-d--fuzzy-p matched))))))
784 (if matched
785 (erc-d--command-consider-prep-success dialog line exes matched)
786 (erc-d--command-consider-prep-fail dialog line exes))))
787
788(defun erc-d--active-ex-p (ring)
789 "Return non-nil when RING has a non-fuzzy exchange.
790That is, return nil when RING is empty or when it only has exchanges
791with leading-tilde tags."
792 (let ((i 0)
793 (len (ring-length ring))
794 ex found)
795 (while (and (not found) (< i len))
796 (unless (erc-d--fuzzy-p (setq ex (ring-ref ring i)))
797 (setq found ex))
798 (cl-incf i))
799 found))
800
801(defun erc-d--finalize-done (dialog)
802 ;; Linger logic for individual dialogs is handled elsewhere
803 (if-let ((finalizer (erc-d-dialog-finalizer dialog)))
804 (funcall finalizer dialog)
805 (let ((d (process-get (erc-d-dialog-process dialog) :dialog-linger-secs)))
806 (push (run-at-time d nil #'erc-d--teardown)
807 (erc-d-dialog-timers dialog)))))
808
809(defun erc-d--advance-or-die (dialog)
810 "Govern the lifetime of DIALOG.
811Replenish exchanges from reader and insert them into the pool of
812expected matches, as produced. Return a symbol indicating session
813status: deferring, matching, depleted, or done."
814 (let ((exes (erc-d-dialog-exchanges dialog))
815 hunk)
816 (cond ((erc-d--active-ex-p exes) 'deferring)
817 ((setq hunk (erc-d-u--read-dialog (erc-d-dialog-hunks dialog)))
818 (let ((exchange (erc-d--create-exchange dialog hunk)))
819 (if (erc-d--fuzzy-p exchange)
820 (ring-insert exes exchange)
821 (ring-insert-at-beginning exes exchange)))
822 'matching)
823 ((not (ring-empty-p exes)) 'depleted)
824 (t 'done))))
825
826(defun erc-d--command-meter-replies (dialog exchange &optional cmd)
827 "Ignore requests until all replies have been sent.
828Do this for some previously matched EXCHANGE in DIALOG based on CMD, a
829symbol. As a side effect, maybe schedule the resumption of the main
830loop after some delay."
831 (let (delay)
832 (if (or (not cmd) (eq 'resume cmd))
833 (when (setq delay (erc-d--send-outgoing dialog exchange))
834 (push (run-at-time delay nil #'erc-d--command-handle-all
835 dialog 'resume)
836 (erc-d-dialog-timers dialog))
837 (erc-d-dialog-state dialog))
838 (setf (erc-d-dialog-state dialog) 'sending))))
839
840(defun erc-d--die-unexpected (dialog)
841 (erc-d--teardown 'error "Received unexpected input: %S"
842 (erc-d-i-message.unparsed (erc-d-dialog-message dialog))))
843
844(defun erc-d--command-refresh (dialog matched)
845 (let ((state (erc-d--advance-or-die dialog)))
846 (when (eq state 'done)
847 (erc-d--finalize-done dialog))
848 (unless matched
849 (when (eq state 'depleted)
850 (erc-d--die-unexpected dialog))
851 (cl-assert (memq state '(matching depleted)) t))
852 (setf (erc-d-dialog-state dialog) state)))
853
854(defun erc-d--command-handle-all (dialog cmd)
855 "Create handler to act as control agent and process DIALOG requests.
856Have it ingest internal control commands (lowercase symbols) and yield
857back others indicating the lifecycle stage of the current dialog."
858 (let ((matched (erc-d-dialog-matched dialog)))
859 (cond
860 (matched
861 (or (erc-d--command-meter-replies dialog matched cmd)
862 (setf (erc-d-dialog-matched dialog) nil)
863 (erc-d--command-refresh dialog t)))
864 ((pcase cmd ; FIXME remove command facility or make extensible
865 ('resume nil)
866 ('eof (erc-d--m (erc-d-dialog-process dialog) "Received an EOF") nil)))
867 (t ; matching
868 (setq matched nil)
869 (catch 'yield
870 (while (not matched)
871 (when (ring-empty-p (erc-d-dialog-exchanges dialog))
872 (erc-d--die-unexpected dialog))
873 (when (setq matched (erc-d--command-consider dialog))
874 (if (eq (car-safe matched) 'error)
875 (apply #'erc-d--teardown matched)
876 (erc-d-on-match dialog matched)
877 (setf (erc-d-dialog-matched dialog) matched)
878 (if-let ((s (erc-d--command-meter-replies dialog matched nil)))
879 (throw 'yield s)
880 (setf (erc-d-dialog-matched dialog) nil))))
881 (erc-d--command-refresh dialog matched)))))))
882
883;;;; Handlers for IRC commands
884
885(cl-defgeneric erc-d-command (dialog cmd)
886 "Handle new CMD from client for DIALOG.
887By default, defer to this dialog's `erc-d--command-handle-all' instance,
888which is stored in its `handler' field.")
889
890(cl-defmethod erc-d-command ((dialog erc-d-dialog) cmd)
891 (when (eq 'sending (erc-d--command-handle-all dialog cmd))
892 (ring-insert-at-beginning (erc-d-dialog-queue dialog)
893 (erc-d-dialog-message dialog))))
894
895;; A similar PONG handler would be useless because we know when to
896;; expect them
897
898(cl-defmethod erc-d-command ((dialog erc-d-dialog) (_cmd (eql PING))
899 &context (erc-d-auto-pong (eql t)))
900 "Respond to PING request from DIALOG peer when ERC-D-AUTO-PONG is t."
901 (let* ((parsed (erc-d-dialog-message dialog))
902 (process (erc-d-dialog-process dialog))
903 (nonce (car (erc-d-i-message.command-args parsed)))
904 (fqdn (erc-d-dialog-server-fqdn dialog)))
905 (erc-d--send process (format ":%s PONG %s :%s" fqdn fqdn nonce))))
906
907
908;;;; Entry points
909
910(defun erc-d-run (host service &optional server-name &rest dialogs)
911 "Start serving DIALOGS on HOST at SERVICE.
912Pass HOST and SERVICE directly to `make-network-process'. When present,
913use string SERVER-NAME for the server-process name as well as that of
914its buffer (w. surrounding asterisks). When absent, do the same with
915`erc-d-server-name'. When running \"in process,\" return the server
916process, otherwise sleep for the duration of the server process.
917
918A dialog must be a symbol matching the base name of a dialog file in
919`erc-d-u-canned-dialog-dir'.
920
921The variable `erc-d-tmpl-vars' determines the common members of the
922`erc-d--render-entries' ENTRIES param. Variables `erc-d-server-fqdn'
923and `erc-d-linger-secs' determine the `erc-d-dialog' items
924`:server-fqdn' and `:linger-secs' for all client processes.
925
926The variable `erc-d-tmpl-vars' can be used to initialize the
927process's `erc-d-dialog' vars item."
928 (when (and server-name (symbolp server-name))
929 (push server-name dialogs)
930 (setq server-name nil))
931 (let (loaded)
932 (dolist (dialog (nreverse dialogs))
933 (let ((reader (erc-d-u--canned-load-dialog dialog)))
934 (when erc-d--slow-mo
935 (setq reader (erc-d-u--rewrite-for-slow-mo erc-d--slow-mo reader)))
936 (push (cons (erc-d-u--normalize-canned-name dialog) reader) loaded)))
937 (setq dialogs loaded))
938 (erc-d--start host service (or server-name erc-d-server-name)
939 :dialog-dialogs dialogs
940 :dialog-vars erc-d-tmpl-vars
941 :dialog-linger-secs erc-d-linger-secs
942 :dialog-server-fqdn erc-d-server-fqdn
943 :dialog-match-handlers (erc-d-u--unkeyword
944 erc-d-match-handlers)))
945
946(defun erc-d-serve ()
947 "Start serving canned dialogs from the command line.
948Although not autoloaded, this function is meant to be summoned via the
949Emacs -f flag while starting a batch session. It prints incoming and
950outgoing messages to standard out.
951
952The main options are --host HOST and --port PORT, which default to
953localhost and auto, respectively. The args are the dialogs to run.
954Unlike with `erc-d-run', dialogs here *must* be files, meaning Lisp-Data
955files adhering to the required format. (These consist of \"specs\"
956detailing timing and template info; see commentary for specifics.)
957
958An optional --add-time N option can also be passed to hike up timeouts
959by some number of seconds N. For example, you might run:
960
961 $ emacs -Q -batch -L . \\
962 > -l erc-d.el \\
963 > -f erc-d-serve \\
964 > --host 192.168.124.1 \\
965 > --port 16667 \\
966 > --add-time 10 \\
967 > ./my-dialog.eld
968
969from a Makefile or manually with \\<global-map>\\[compile]. And then in
970another terminal, do:
971
972 $ nc -C 192.168.124.1 16667 ; or telnet if your nc doesn't have -C
973 > PASS changeme
974 ...
975
976Use `erc-d-run' instead to start the server from within Emacs."
977 (unless noninteractive
978 (error "Command-line func erc-d-serve not run in -batch session"))
979 (setq erc-d--in-process nil)
980 (let (port host dialogs erc-d--slow-mo)
981 (while command-line-args-left
982 (pcase (pop command-line-args-left)
983 ("--add-time" (setq erc-d--slow-mo
984 (string-to-number (pop command-line-args-left))))
985 ("--linger" (setq erc-d-linger-secs
986 (string-to-number (pop command-line-args-left))))
987 ("--host" (setq host (pop command-line-args-left)))
988 ("--port" (setq port (string-to-number (pop command-line-args-left))))
989 (dialog (push dialog dialogs))))
990 (setq dialogs (mapcar #'erc-d-u--massage-canned-name dialogs))
991 (when erc-d--slow-mo
992 (message "Slow mo is ON"))
993 (apply #'erc-d-run (or host "localhost") port nil (nreverse dialogs))))
994
995(provide 'erc-d)
996
997;;; erc-d.el ends here
diff --git a/test/lisp/erc/resources/erc-d/resources/basic.eld b/test/lisp/erc/resources/erc-d/resources/basic.eld
new file mode 100644
index 00000000000..a020eec3fff
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/basic.eld
@@ -0,0 +1,32 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) "changeme"))
4((nick 0.2 "NICK tester"))
5
6((user 0.2 "USER user 0 * :tester")
7 (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester")
8 (0 ":irc.example.org 002 tester :Your host is irc.example.org")
9 (0 ":irc.example.org 003 tester :This server was created just now")
10 (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
12 " :are supported by this server")
13 (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
14 ;; Just to mix thing's up (force handler to schedule timer)
15 (0.1 ":irc.example.org 252 tester 0 :IRC Operators online")
16 (0 ":irc.example.org 253 tester 0 :unregistered connections")
17 (0 ":irc.example.org 254 tester 1 :channels formed")
18 (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers")
19 (0.1 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3")
20 (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3")
21 (0 ":irc.example.org 422 tester :MOTD File is missing"))
22
23((mode-user 5 "MODE tester +i")
24 (0 ":irc.example.org 221 tester +Zi")
25 (0 ":irc.example.org 306 tester :You have been marked as being away")
26 (0 ":tester!~tester@localhost JOIN #chan")
27 (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org")
28 (0 ":irc.example.org 366 alice #chan :End of NAMES list"))
29
30;; Some comment (to prevent regression)
31((mode-chan 1.2 "MODE #chan")
32 (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey"))
diff --git a/test/lisp/erc/resources/erc-d/resources/depleted.eld b/test/lisp/erc/resources/erc-d/resources/depleted.eld
new file mode 100644
index 00000000000..e5a7f03efb7
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/depleted.eld
@@ -0,0 +1,12 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS :changeme"))
4
5((~fake 3.2 "FAKE ")
6 (0.1 ":irc.example.org FAKE irc.example.com :ok"))
7
8((nick 0.2 "NICK tester"))
9
10((user 0.2 "USER user 0 * :tester")
11 (0 ":irc.example.org 001 tester :Welcome to the Internet tester")
12 (0 ":irc.example.org 422 tester :MOTD File is missing"))
diff --git a/test/lisp/erc/resources/erc-d/resources/drop-a.eld b/test/lisp/erc/resources/erc-d/resources/drop-a.eld
new file mode 100644
index 00000000000..2e23eeb20ff
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/drop-a.eld
@@ -0,0 +1,4 @@
1;; -*- mode: lisp-data; -*-
2((pass 1 "PASS " (? ?:) "a")
3 (0 "hi"))
4((drop 0.01 DROP))
diff --git a/test/lisp/erc/resources/erc-d/resources/drop-b.eld b/test/lisp/erc/resources/erc-d/resources/drop-b.eld
new file mode 100644
index 00000000000..facecd5e812
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/drop-b.eld
@@ -0,0 +1,4 @@
1;; -*- mode: lisp-data; -*-
2((pass 1 "PASS " (? ?:) "b")
3 (0 "hi"))
4((linger 1 LINGER))
diff --git a/test/lisp/erc/resources/erc-d/resources/dynamic-barnet.eld b/test/lisp/erc/resources/erc-d/resources/dynamic-barnet.eld
new file mode 100644
index 00000000000..36b1cc23081
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/dynamic-barnet.eld
@@ -0,0 +1,33 @@
1;;; -*- mode: lisp-data -*-
2((fake 0 "FAKE noop"))
3
4((nick 1.2 "NICK tester"))
5
6((user 2.2 "USER user 0 * :tester")
7 (0. ":irc.barnet.org 001 tester :Welcome to the BAR Network tester")
8 (0. ":irc.barnet.org 002 tester :Your host is irc.barnet.org")
9 (0. ":irc.barnet.org 003 tester :This server was created just now")
10 (0. ":irc.barnet.org 004 tester irc.barnet.org BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0. ":irc.barnet.org 005 tester MODES NETWORK=BarNet NICKLEN=32 PREFIX=(qaohv)~&@%+ :are supported by this server")
12 (0. ":irc.barnet.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
13 (0. ":irc.barnet.org 252 tester 0 :IRC Operators online")
14 (0. ":irc.barnet.org 253 tester 0 :unregistered connections")
15 (0. ":irc.barnet.org 254 tester 1 :channels formed")
16 (0. ":irc.barnet.org 255 tester :I have 3 clients and 0 servers")
17 (0. ":irc.barnet.org 265 tester 3 3 :Current local users 3, max 3")
18 (0. ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
19 (0. ":irc.barnet.org 422 tester :MOTD File is missing"))
20
21((mode-user 1.2 "MODE tester +i")
22 (0. ":irc.barnet.org 221 tester +Zi")
23 (0. ":irc.barnet.org 306 tester :You have been marked as being away")
24 (0 ":tester!~u@awyxgybtkx7uq.irc JOIN #chan")
25 (0 ":irc.barnet.org 353 joe = #chan :+joe!~joe@example.com @%+mike!~mike@example.org")
26 (0 ":irc.barnet.org 366 joe #chan :End of NAMES list"))
27
28((mode 1 "MODE #chan")
29 (0 ":irc.barnet.org 324 tester #chan +nt")
30 (0 ":irc.barnet.org 329 tester #chan 1620805269")
31 (0.1 ":joe!~u@awyxgybtkx7uq.irc PRIVMSG #chan :mike: Yes, a dozen; and as many to the vantage, as would store the world they played for.")
32 (0.05 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :joe: As he regards his aged father's life.")
33 (0.05 ":joe!~u@awyxgybtkx7uq.irc PRIVMSG #chan :mike: It is a rupture that you may easily heal; and the cure of it not only saves your brother, but keeps you from dishonour in doing it."))
diff --git a/test/lisp/erc/resources/erc-d/resources/dynamic-foonet.eld b/test/lisp/erc/resources/erc-d/resources/dynamic-foonet.eld
new file mode 100644
index 00000000000..5dbea50f865
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/dynamic-foonet.eld
@@ -0,0 +1,32 @@
1;;; -*- mode: lisp-data -*-
2
3((nick 1.2 "NICK tester"))
4
5((user 2.2 "USER user 0 * :tester")
6 (0. ":irc.foonet.org 001 tester :Welcome to the FOO Network tester")
7 (0. ":irc.foonet.org 002 tester :Your host is irc.foonet.org")
8 (0. ":irc.foonet.org 003 tester :This server was created just now")
9 (0. ":irc.foonet.org 004 tester irc.foonet.org BERios CEIRabehiklmnoqstv Iabehkloqv")
10 (0. ":irc.foonet.org 005 tester MODES NETWORK=FooNet NICKLEN=32 PREFIX=(qaohv)~&@%+ :are supported by this server")
11 (0. ":irc.foonet.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
12 (0. ":irc.foonet.org 252 tester 0 :IRC Operators online")
13 (0. ":irc.foonet.org 253 tester 0 :unregistered connections")
14 (0. ":irc.foonet.org 254 tester 1 :channels formed")
15 (0. ":irc.foonet.org 255 tester :I have 3 clients and 0 servers")
16 (0. ":irc.foonet.org 265 tester 3 3 :Current local users 3, max 3")
17 (0. ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
18 (0. ":irc.foonet.org 422 tester :MOTD File is missing"))
19
20((mode-user 1.2 "MODE tester +i")
21 (0. ":irc.foonet.org 221 tester +Zi")
22 (0. ":irc.foonet.org 306 tester :You have been marked as being away")
23 (0 ":tester!~u@awyxgybtkx7uq.irc JOIN #chan")
24 (0 ":irc.foonet.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org")
25 (0 ":irc.foonet.org 366 alice #chan :End of NAMES list"))
26
27((mode 2 "MODE #chan")
28 (0 ":irc.foonet.org 324 tester #chan +nt")
29 (0 ":irc.foonet.org 329 tester #chan 1620805269")
30 (0.1 ":alice!~u@awyxgybtkx7uq.irc PRIVMSG #chan :bob: Yes, a dozen; and as many to the vantage, as would store the world they played for.")
31 (0.05 ":bob!~u@awyxgybtkx7uq.irc PRIVMSG #chan :alice: As he regards his aged father's life.")
32 (0.05 ":alice!~u@awyxgybtkx7uq.irc PRIVMSG #chan :bob: It is a rupture that you may easily heal; and the cure of it not only saves your brother, but keeps you from dishonour in doing it."))
diff --git a/test/lisp/erc/resources/erc-d/resources/dynamic-stub.eld b/test/lisp/erc/resources/erc-d/resources/dynamic-stub.eld
new file mode 100644
index 00000000000..d93313023d0
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/dynamic-stub.eld
@@ -0,0 +1,4 @@
1;;; -*- mode: lisp-data -*-
2((pass 10.0 "PASS " (? ?:) token ":changeme"))
3
4((fake 0 "FAKE"))
diff --git a/test/lisp/erc/resources/erc-d/resources/dynamic.eld b/test/lisp/erc/resources/erc-d/resources/dynamic.eld
new file mode 100644
index 00000000000..459b6e52bfe
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/dynamic.eld
@@ -0,0 +1,30 @@
1;;; -*- mode: lisp-data -*-
2((pass 10.0 "PASS " (? ?:) "changeme"))
3((nick 2.2 "NICK tester"))
4
5((user 2.2 "USER " user " " (ignored digit "*") " :" realname)
6 (0.0 ":" dom " 001 " nick " :Welcome to the Internet Relay Network tester")
7 (0.0 ":" dom " 002 " nick " :Your host is " dom)
8 (0.0 ":" dom " 003 " nick " :This server was created just now")
9 (0.0 ":" dom " 004 " nick " " dom " BERios CEIRabehiklmnoqstv Iabehkloqv")
10 (0.0 ":" dom " 005 " nick " MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
11 " :are supported by this server")
12 (0.0 ":" dom " 251 " nick " :There are 3 users and 0 invisible on 1 server(s)")
13 (0.0 ":" dom " 252 " nick " 0 :IRC Operators online")
14 (0.0 ":" dom " 253 " nick " 0 :unregistered connections")
15 (0.0 ":" dom " 254 " nick " 1 :channels formed")
16 (0.0 ":" dom " 255 " nick " :I have 3 clients and 0 servers")
17 (0.0 ":" dom " 265 " nick " 3 3 :Current local users 3, max 3")
18 (0.0 ":" dom " 266 " nick " 3 3 :Current global users 3, max 3")
19 (0.0 ":" dom " 422 " nick " :MOTD File is missing"))
20
21((mode-user 2.2 "MODE tester +i")
22 (0.0 ":" dom " 221 " nick " +Zi")
23
24 (0.0 ":" dom " 306 " nick " :You have been marked as being away")
25 (0.0 ":" nick "!~" nick "@localhost JOIN #chan")
26 (0.0 ":" dom " 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org")
27 (0.0 ":" dom " 366 alice #chan :End of NAMES list"))
28
29((mode 2.2 "MODE #chan")
30 (0.1 ":bob!~bob@example.org PRIVMSG #chan :" nick ": hey"))
diff --git a/test/lisp/erc/resources/erc-d/resources/eof.eld b/test/lisp/erc/resources/erc-d/resources/eof.eld
new file mode 100644
index 00000000000..5da84b2e74f
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/eof.eld
@@ -0,0 +1,33 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) "changeme"))
4((nick 0.2 "NICK tester"))
5
6((user 0.2 "USER user 0 * :tester")
7 (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester")
8 (0 ":irc.example.org 002 tester :Your host is irc.example.org")
9 (0 ":irc.example.org 003 tester :This server was created just now")
10 (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
12 " :are supported by this server")
13 (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
14 ;; Just to mix thing's up (force handler to schedule timer)
15 (0.1 ":irc.example.org 252 tester 0 :IRC Operators online")
16 (0 ":irc.example.org 253 tester 0 :unregistered connections")
17 (0 ":irc.example.org 254 tester 1 :channels formed")
18 (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers")
19 (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3")
20 (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3")
21 (0 ":irc.example.org 422 tester :MOTD File is missing"))
22
23((mode-user 1.2 "MODE tester +i")
24 (0 ":irc.example.org 221 tester +Zi")
25 (0 ":irc.example.org 306 tester :You have been marked as being away")
26 (0 ":tester!~tester@localhost JOIN #chan")
27 (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org")
28 (0 ":irc.example.org 366 alice #chan :End of NAMES list"))
29
30((mode-chan 1.2 "MODE #chan")
31 (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey"))
32
33((eof 1.0 EOF))
diff --git a/test/lisp/erc/resources/erc-d/resources/fuzzy.eld b/test/lisp/erc/resources/erc-d/resources/fuzzy.eld
new file mode 100644
index 00000000000..0504b6a6682
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/fuzzy.eld
@@ -0,0 +1,42 @@
1;;; -*- mode: lisp-data -*-
2((pass 10.0 "PASS " (? ?:) "changeme"))
3((nick 0.2 "NICK tester"))
4
5((user 0.5 "USER user 0 * :tester")
6 (0.0 "@time=" now " :irc.org 001 tester :Welcome to the Internet Relay Network tester")
7 (0.0 "@time=" now " :irc.org 002 tester :Your host is irc.org")
8 (0.0 "@time=" now " :irc.org 003 tester :This server was created just now")
9 (0.0 "@time=" now " :irc.org 004 tester irc.org BERios CEIRabehiklmnoqstv Iabehkloqv")
10 (0.0 "@time=" now " :irc.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+ :are supported by this server")
11 (0.0 "@time=" now " :irc.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
12 (0.0 "@time=" now " :irc.org 252 tester 0 :IRC Operators online")
13 (0.0 "@time=" now " :irc.org 253 tester 0 :unregistered connections")
14 (0.0 "@time=" now " :irc.org 254 tester 1 :channels formed")
15 (0.0 "@time=" now " :irc.org 255 tester :I have 3 clients and 0 servers")
16 (0.0 "@time=" now " :irc.org 265 tester 3 3 :Current local users 3, max 3")
17 (0.0 "@time=" now " :irc.org 266 tester 3 3 :Current global users 3, max 3")
18 (0.0 "@time=" now " :irc.org 422 tester :MOTD File is missing"))
19
20((mode-user 1.2 "MODE tester +i")
21 (0.0 "@time=" now " :irc.org 221 tester +Zi")
22 (0.0 "@time=" now " :irc.org 306 tester :You have been marked as being away"))
23
24((~join-foo 3.2 "JOIN #foo")
25 (0 "@time=" now " :tester!~tester@localhost JOIN #foo")
26 (0 "@time=" now " :irc.example.org 353 alice = #foo :+alice!~alice@example.com @%+bob!~bob@example.org")
27 (0 "@time=" now " :irc.example.org 366 alice #foo :End of NAMES list"))
28
29((~join-bar 1.2 "JOIN #bar")
30 (0 "@time=" now " :tester!~tester@localhost JOIN #bar")
31 (0 "@time=" now " :irc.example.org 353 alice = #bar :+alice!~alice@example.com @%+bob!~bob@example.org")
32 (0 "@time=" now " :irc.example.org 366 alice #bar :End of NAMES list"))
33
34((~mode-foo 3.2 "MODE #foo")
35 (0.0 "@time=" now " :irc.example.org 324 tester #foo +Cint")
36 (0.0 "@time=" now " :irc.example.org 329 tester #foo 1519850102")
37 (0.1 "@time=" now " :bob!~bob@example.org PRIVMSG #foo :hey"))
38
39((mode-bar 10.2 "MODE #bar")
40 (0.0 "@time=" now " :irc.example.org 324 tester #bar +HMfnrt 50:5h :10:5")
41 (0.0 "@time=" now " :irc.example.org 329 tester #bar :1602642829")
42 (0.1 "@time=" now " :alice!~alice@example.com PRIVMSG #bar :hi"))
diff --git a/test/lisp/erc/resources/erc-d/resources/incremental.eld b/test/lisp/erc/resources/erc-d/resources/incremental.eld
new file mode 100644
index 00000000000..ab940fe6129
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/incremental.eld
@@ -0,0 +1,43 @@
1;;; -*- mode: lisp-data -*-
2((pass 10.0 "PASS " (? ?:) "changeme"))
3((nick 0.2 "NICK tester"))
4
5((user 0.2 "USER user 0 * :tester")
6 (0.0 ":irc.foo.net 001 tester :Welcome to the Internet Relay Network tester")
7 (0.0 ":irc.foo.net 002 tester :Your host is irc.foo.net")
8 (0.0 ":irc.foo.net 003 tester :This server was created just now")
9 (0.0 ":irc.foo.net 004 tester irc.foo.net BERios CEIRabehiklmnoqstv Iabehkloqv")
10 (0.0 ":irc.foo.net 005 tester MODES NETWORK=FooNet NICKLEN=32 PREFIX=(qaohv)~&@%+"
11 " :are supported by this server")
12 (0.0 ":irc.foo.net 251 tester :There are 3 users and 0 invisible on 1 server(s)")
13 (0.0 ":irc.foo.net 252 tester 0 :IRC Operators online")
14 (0.0 ":irc.foo.net 253 tester 0 :unregistered connections")
15 (0.0 ":irc.foo.net 254 tester 1 :channels formed")
16 (0.0 ":irc.foo.net 255 tester :I have 3 clients and 0 servers")
17 (0.0 ":irc.foo.net 265 tester 3 3 :Current local users 3, max 3")
18 (0.0 ":irc.foo.net 266 tester 3 3 :Current global users 3, max 3")
19 (0.0 ":irc.foo.net 422 tester :MOTD File is missing"))
20
21((mode-user 1.2 "MODE tester +i")
22 (0.0 ":irc.foo.net 221 tester +Zi")
23 (0.0 ":irc.foo.net 306 tester :You have been marked as being away"))
24
25((join 3 "JOIN #foo")
26 (0 ":tester!~tester@localhost JOIN #foo")
27 (0 ":irc.foo.net 353 alice = #foo :+alice!~alice@example.com @%+bob!~bob@example.org")
28 (0 ":irc.foo.net 366 alice #foo :End of NAMES list"))
29
30((mode 3 "MODE #foo")
31 (0.0 ":irc.foo.net 324 tester #foo +Cint")
32 (0.0 ":irc.foo.net 329 tester #foo 1519850102")
33 (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defence, by mercy, 'tis most just.")
34 (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: Grows, lives, and dies, in single blessedness.")
35 (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :Look for me.")
36 (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: By this hand, it will not kill a fly. But come, now I will be your Rosalind in a more coming-on disposition; and ask me what you will, I will grant it.")
37 (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: That I must love a loathed enemy.")
38 (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: As't please your lordship: I'll leave you.")
39 (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: Then there is no true lover in the forest; else sighing every minute and groaning every hour would detect the lazy foot of Time as well as a clock.")
40 (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: His discretion, I am sure, cannot carry his valour, for the goose carries not the fox. It is well: leave it to his discretion, and let us listen to the moon.")
41 (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :Done"))
42
43((hi 10 "PRIVMSG #foo :Hi"))
diff --git a/test/lisp/erc/resources/erc-d/resources/irc-parser-tests.eld b/test/lisp/erc/resources/erc-d/resources/irc-parser-tests.eld
new file mode 100644
index 00000000000..168569f5481
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/irc-parser-tests.eld
@@ -0,0 +1,380 @@
1;;; -*- mode: lisp-data; -*-
2
3;; https://github.com/DanielOaks/irc-parser-tests
4((mask-match
5 (tests
6 ((mask . "*@127.0.0.1")
7 (matches "coolguy!ab@127.0.0.1" "cooldud3!~bc@127.0.0.1")
8 (fails "coolguy!ab@127.0.0.5" "cooldud3!~d@124.0.0.1"))
9 ((mask . "cool*@*")
10 (matches "coolguy!ab@127.0.0.1" "cooldud3!~bc@127.0.0.1" "cool132!ab@example.com")
11 (fails "koolguy!ab@127.0.0.5" "cooodud3!~d@124.0.0.1"))
12 ((mask . "cool!*@*")
13 (matches "cool!guyab@127.0.0.1" "cool!~dudebc@127.0.0.1" "cool!312ab@example.com")
14 (fails "coolguy!ab@127.0.0.1" "cooldud3!~bc@127.0.0.1" "koolguy!ab@127.0.0.5" "cooodud3!~d@124.0.0.1"))
15 ((mask . "cool!?username@*")
16 (matches "cool!ausername@127.0.0.1" "cool!~username@127.0.0.1")
17 (fails "cool!username@127.0.0.1"))
18 ((mask . "cool!a?*@*")
19 (matches "cool!ab@127.0.0.1" "cool!abc@127.0.0.1")
20 (fails "cool!a@127.0.0.1"))
21 ((mask . "cool[guy]!*@*")
22 (matches "cool[guy]!guy@127.0.0.1" "cool[guy]!a@example.com")
23 (fails "coolg!ab@127.0.0.1" "cool[!ac@127.0.1.1"))))
24 (msg-join
25 (tests
26 ((desc . "Simple test with verb and params.")
27 (atoms
28 (verb . "foo")
29 (params "bar" "baz" "asdf"))
30 (matches "foo bar baz asdf" "foo bar baz :asdf"))
31 ((desc . "Simple test with source and no params.")
32 (atoms
33 (source . "src")
34 (verb . "AWAY"))
35 (matches ":src AWAY"))
36 ((desc . "Simple test with source and empty trailing param.")
37 (atoms
38 (source . "src")
39 (verb . "AWAY")
40 (params ""))
41 (matches ":src AWAY :"))
42 ((desc . "Simple test with source.")
43 (atoms
44 (source . "coolguy")
45 (verb . "foo")
46 (params "bar" "baz" "asdf"))
47 (matches ":coolguy foo bar baz asdf" ":coolguy foo bar baz :asdf"))
48 ((desc . "Simple test with trailing param.")
49 (atoms
50 (verb . "foo")
51 (params "bar" "baz" "asdf quux"))
52 (matches "foo bar baz :asdf quux"))
53 ((desc . "Simple test with empty trailing param.")
54 (atoms
55 (verb . "foo")
56 (params "bar" "baz" ""))
57 (matches "foo bar baz :"))
58 ((desc . "Simple test with trailing param containing colon.")
59 (atoms
60 (verb . "foo")
61 (params "bar" "baz" ":asdf"))
62 (matches "foo bar baz ::asdf"))
63 ((desc . "Test with source and trailing param.")
64 (atoms
65 (source . "coolguy")
66 (verb . "foo")
67 (params "bar" "baz" "asdf quux"))
68 (matches ":coolguy foo bar baz :asdf quux"))
69 ((desc . "Test with trailing containing beginning+end whitespace.")
70 (atoms
71 (source . "coolguy")
72 (verb . "foo")
73 (params "bar" "baz" " asdf quux "))
74 (matches ":coolguy foo bar baz : asdf quux "))
75 ((desc . "Test with trailing containing what looks like another trailing param.")
76 (atoms
77 (source . "coolguy")
78 (verb . "PRIVMSG")
79 (params "bar" "lol :) "))
80 (matches ":coolguy PRIVMSG bar :lol :) "))
81 ((desc . "Simple test with source and empty trailing.")
82 (atoms
83 (source . "coolguy")
84 (verb . "foo")
85 (params "bar" "baz" ""))
86 (matches ":coolguy foo bar baz :"))
87 ((desc . "Trailing contains only spaces.")
88 (atoms
89 (source . "coolguy")
90 (verb . "foo")
91 (params "bar" "baz" " "))
92 (matches ":coolguy foo bar baz : "))
93 ((desc . "Param containing tab (tab is not considered SPACE for message splitting).")
94 (atoms
95 (source . "coolguy")
96 (verb . "foo")
97 (params "b ar" "baz"))
98 (matches ":coolguy foo b ar baz" ":coolguy foo b ar :baz"))
99 ((desc . "Tag with no value and space-filled trailing.")
100 (atoms
101 (tags
102 (asd . ""))
103 (source . "coolguy")
104 (verb . "foo")
105 (params "bar" "baz" " "))
106 (matches "@asd :coolguy foo bar baz : "))
107 ((desc . "Tags with escaped values.")
108 (atoms
109 (verb . "foo")
110 (tags
111 (a . "b\\and\nk")
112 (d . "gh;764")))
113 (matches "@a=b\\\\and\\nk;d=gh\\:764 foo" "@d=gh\\:764;a=b\\\\and\\nk foo"))
114 ((desc . "Tags with escaped values and params.")
115 (atoms
116 (verb . "foo")
117 (tags
118 (a . "b\\and\nk")
119 (d . "gh;764"))
120 (params "par1" "par2"))
121 (matches "@a=b\\\\and\\nk;d=gh\\:764 foo par1 par2" "@a=b\\\\and\\nk;d=gh\\:764 foo par1 :par2" "@d=gh\\:764;a=b\\\\and\\nk foo par1 par2" "@d=gh\\:764;a=b\\\\and\\nk foo par1 :par2"))
122 ((desc . "Tag with long, strange values (including LF and newline).")
123 (atoms
124 (tags
125 (foo . "\\\\;\\s \n"))
126 (verb . "COMMAND"))
127 (matches "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"))))
128 (msg-split
129 (tests
130 ((input . "foo bar baz asdf")
131 (atoms
132 (verb . "foo")
133 (params "bar" "baz" "asdf")))
134 ((input . ":coolguy foo bar baz asdf")
135 (atoms
136 (source . "coolguy")
137 (verb . "foo")
138 (params "bar" "baz" "asdf")))
139 ((input . "foo bar baz :asdf quux")
140 (atoms
141 (verb . "foo")
142 (params "bar" "baz" "asdf quux")))
143 ((input . "foo bar baz :")
144 (atoms
145 (verb . "foo")
146 (params "bar" "baz" "")))
147 ((input . "foo bar baz ::asdf")
148 (atoms
149 (verb . "foo")
150 (params "bar" "baz" ":asdf")))
151 ((input . ":coolguy foo bar baz :asdf quux")
152 (atoms
153 (source . "coolguy")
154 (verb . "foo")
155 (params "bar" "baz" "asdf quux")))
156 ((input . ":coolguy foo bar baz : asdf quux ")
157 (atoms
158 (source . "coolguy")
159 (verb . "foo")
160 (params "bar" "baz" " asdf quux ")))
161 ((input . ":coolguy PRIVMSG bar :lol :) ")
162 (atoms
163 (source . "coolguy")
164 (verb . "PRIVMSG")
165 (params "bar" "lol :) ")))
166 ((input . ":coolguy foo bar baz :")
167 (atoms
168 (source . "coolguy")
169 (verb . "foo")
170 (params "bar" "baz" "")))
171 ((input . ":coolguy foo bar baz : ")
172 (atoms
173 (source . "coolguy")
174 (verb . "foo")
175 (params "bar" "baz" " ")))
176 ((input . "@a=b;c=32;k;rt=ql7 foo")
177 (atoms
178 (verb . "foo")
179 (tags
180 (a . "b")
181 (c . "32")
182 (k . "")
183 (rt . "ql7"))))
184 ((input . "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo")
185 (atoms
186 (verb . "foo")
187 (tags
188 (a . "b\\and\nk")
189 (c . "72 45")
190 (d . "gh;764"))))
191 ((input . "@c;h=;a=b :quux ab cd")
192 (atoms
193 (tags
194 (c . "")
195 (h . "")
196 (a . "b"))
197 (source . "quux")
198 (verb . "ab")
199 (params "cd")))
200 ((input . ":src JOIN #chan")
201 (atoms
202 (source . "src")
203 (verb . "JOIN")
204 (params "#chan")))
205 ((input . ":src JOIN :#chan")
206 (atoms
207 (source . "src")
208 (verb . "JOIN")
209 (params "#chan")))
210 ((input . ":src AWAY")
211 (atoms
212 (source . "src")
213 (verb . "AWAY")))
214 ((input . ":src AWAY ")
215 (atoms
216 (source . "src")
217 (verb . "AWAY")))
218 ((input . ":cool guy foo bar baz")
219 (atoms
220 (source . "cool guy")
221 (verb . "foo")
222 (params "bar" "baz")))
223 ((input . ":coolguy!ag@net5work.admin PRIVMSG foo :bar baz")
224 (atoms
225 (source . "coolguy!ag@net5work.admin")
226 (verb . "PRIVMSG")
227 (params "foo" "bar baz")))
228 ((input . ":coolguy!~ag@net05work.admin PRIVMSG foo :bar baz")
229 (atoms
230 (source . "coolguy!~ag@net05work.admin")
231 (verb . "PRIVMSG")
232 (params "foo" "bar baz")))
233 ((input . "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3")
234 (atoms
235 (tags
236 (tag1 . "value1")
237 (tag2 . "")
238 (vendor1/tag3 . "value2")
239 (vendor2/tag4 . ""))
240 (source . "irc.example.com")
241 (verb . "COMMAND")
242 (params "param1" "param2" "param3 param3")))
243 ((input . ":irc.example.com COMMAND param1 param2 :param3 param3")
244 (atoms
245 (source . "irc.example.com")
246 (verb . "COMMAND")
247 (params "param1" "param2" "param3 param3")))
248 ((input . "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 COMMAND param1 param2 :param3 param3")
249 (atoms
250 (tags
251 (tag1 . "value1")
252 (tag2 . "")
253 (vendor1/tag3 . "value2")
254 (vendor2/tag4 . ""))
255 (verb . "COMMAND")
256 (params "param1" "param2" "param3 param3")))
257 ((input . "COMMAND")
258 (atoms
259 (verb . "COMMAND")))
260 ((input . "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND")
261 (atoms
262 (tags
263 (foo . "\\\\;\\s \n"))
264 (verb . "COMMAND")))
265 ((input . ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters")
266 (atoms
267 (source . "gravel.mozilla.org")
268 (verb . "432")
269 (params "#momo" "Erroneous Nickname: Illegal characters")))
270 ((input . ":gravel.mozilla.org MODE #tckk +n ")
271 (atoms
272 (source . "gravel.mozilla.org")
273 (verb . "MODE")
274 (params "#tckk" "+n")))
275 ((input . ":services.esper.net MODE #foo-bar +o foobar ")
276 (atoms
277 (source . "services.esper.net")
278 (verb . "MODE")
279 (params "#foo-bar" "+o" "foobar")))
280 ((input . "@tag1=value\\\\ntest COMMAND")
281 (atoms
282 (tags
283 (tag1 . "value\\ntest"))
284 (verb . "COMMAND")))
285 ((input . "@tag1=value\\1 COMMAND")
286 (atoms
287 (tags
288 (tag1 . "value1"))
289 (verb . "COMMAND")))
290 ((input . "@tag1=value1\\ COMMAND")
291 (atoms
292 (tags
293 (tag1 . "value1"))
294 (verb . "COMMAND")))
295 ((input . "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND")
296 (atoms
297 (tags
298 (tag1 . "5")
299 (tag2 . "3")
300 (tag3 . "4"))
301 (verb . "COMMAND")))
302 ((input . "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND")
303 (atoms
304 (tags
305 (tag1 . "5")
306 (tag2 . "3")
307 (tag3 . "4")
308 (vendor/tag2 . "8"))
309 (verb . "COMMAND")))
310 ((input . ":SomeOp MODE #channel :+i")
311 (atoms
312 (source . "SomeOp")
313 (verb . "MODE")
314 (params "#channel" "+i")))
315 ((input . ":SomeOp MODE #channel +oo SomeUser :AnotherUser")
316 (atoms
317 (source . "SomeOp")
318 (verb . "MODE")
319 (params "#channel" "+oo" "SomeUser" "AnotherUser")))))
320 (userhost-split
321 (tests
322 ((source . "coolguy")
323 (atoms
324 (nick . "coolguy")))
325 ((source . "coolguy!ag@127.0.0.1")
326 (atoms
327 (nick . "coolguy")
328 (user . "ag")
329 (host . "127.0.0.1")))
330 ((source . "coolguy!~ag@localhost")
331 (atoms
332 (nick . "coolguy")
333 (user . "~ag")
334 (host . "localhost")))
335 ((source . "coolguy@127.0.0.1")
336 (atoms
337 (nick . "coolguy")
338 (host . "127.0.0.1")))
339 ((source . "coolguy!ag")
340 (atoms
341 (nick . "coolguy")
342 (user . "ag")))
343 ((source . "coolguy!ag@net5work.admin")
344 (atoms
345 (nick . "coolguy")
346 (user . "ag")
347 (host . "net5work.admin")))
348 ((source . "coolguy!~ag@net05work.admin")
349 (atoms
350 (nick . "coolguy")
351 (user . "~ag")
352 (host . "net05work.admin")))))
353 (validate-hostname
354 (tests
355 ((host . "irc.example.com")
356 (valid . t))
357 ((host . "i.coolguy.net")
358 (valid . t))
359 ((host . "irc-srv.net.uk")
360 (valid . t))
361 ((host . "iRC.CooLguY.NeT")
362 (valid . t))
363 ((host . "gsf.ds342.co.uk")
364 (valid . t))
365 ((host . "324.net.uk")
366 (valid . t))
367 ((host . "xn--bcher-kva.ch")
368 (valid . t))
369 ((host . "-lol-.net.uk")
370 (valid . :false))
371 ((host . "-lol.net.uk")
372 (valid . :false))
373 ((host . "_irc._sctp.lol.net.uk")
374 (valid . :false))
375 ((host . "irc")
376 (valid . :false))
377 ((host . "com")
378 (valid . :false))
379 ((host . "")
380 (valid . :false)))))
diff --git a/test/lisp/erc/resources/erc-d/resources/linger-multi-a.eld b/test/lisp/erc/resources/erc-d/resources/linger-multi-a.eld
new file mode 100644
index 00000000000..751500537d9
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/linger-multi-a.eld
@@ -0,0 +1,3 @@
1;; -*- mode: lisp-data; -*-
2((pass 1 "PASS " (? ?:) "a"))
3((linger 100 LINGER)) \ No newline at end of file
diff --git a/test/lisp/erc/resources/erc-d/resources/linger-multi-b.eld b/test/lisp/erc/resources/erc-d/resources/linger-multi-b.eld
new file mode 100644
index 00000000000..c906c9e649b
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/linger-multi-b.eld
@@ -0,0 +1,3 @@
1;; -*- mode: lisp-data; -*-
2((pass 1 "PASS " (? ?:) "b"))
3((linger 1 LINGER))
diff --git a/test/lisp/erc/resources/erc-d/resources/linger.eld b/test/lisp/erc/resources/erc-d/resources/linger.eld
new file mode 100644
index 00000000000..36c81a3af4b
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/linger.eld
@@ -0,0 +1,33 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) "changeme"))
4((nick 0.2 "NICK tester"))
5
6((user 0.2 "USER user 0 * :tester")
7 (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester")
8 (0 ":irc.example.org 002 tester :Your host is irc.example.org")
9 (0 ":irc.example.org 003 tester :This server was created just now")
10 (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
12 " :are supported by this server")
13 (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
14 ;; Just to mix thing's up (force handler to schedule timer)
15 (0.1 ":irc.example.org 252 tester 0 :IRC Operators online")
16 (0 ":irc.example.org 253 tester 0 :unregistered connections")
17 (0 ":irc.example.org 254 tester 1 :channels formed")
18 (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers")
19 (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3")
20 (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3")
21 (0 ":irc.example.org 422 tester :MOTD File is missing"))
22
23((mode-user 1.2 "MODE tester +i")
24 (0 ":irc.example.org 221 tester +Zi")
25 (0 ":irc.example.org 306 tester :You have been marked as being away")
26 (0 ":tester!~tester@localhost JOIN #chan")
27 (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org")
28 (0 ":irc.example.org 366 alice #chan :End of NAMES list"))
29
30((mode-chan 1.2 "MODE #chan")
31 (0 ":bob!~bob@example.org PRIVMSG #chan :hey"))
32
33((linger 1.0 LINGER))
diff --git a/test/lisp/erc/resources/erc-d/resources/no-block.eld b/test/lisp/erc/resources/erc-d/resources/no-block.eld
new file mode 100644
index 00000000000..1b1f3965637
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/no-block.eld
@@ -0,0 +1,55 @@
1;;; -*- mode: lisp-data -*-
2((pass 10.0 "PASS " (? ?:) "changeme"))
3((nick 0.2 "NICK tester"))
4
5((user 0.2 "USER user 0 * :tester")
6 (0.0 ":irc.org 001 tester :Welcome to the Internet Relay Network tester")
7 (0.0 ":irc.org 002 tester :Your host is irc.org")
8 (0.0 ":irc.org 003 tester :This server was created just now")
9 (0.0 ":irc.org 004 tester irc.org BERios CEIRabehiklmnoqstv Iabehkloqv")
10 (0.0 ":irc.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
11 " :are supported by this server")
12 (0.0 ":irc.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
13 (0.0 ":irc.org 252 tester 0 :IRC Operators online")
14 (0.0 ":irc.org 253 tester 0 :unregistered connections")
15 (0.0 ":irc.org 254 tester 1 :channels formed")
16 (0.0 ":irc.org 255 tester :I have 3 clients and 0 servers")
17 (0.0 ":irc.org 265 tester 3 3 :Current local users 3, max 3")
18 (0.0 ":irc.org 266 tester 3 3 :Current global users 3, max 3")
19 (0.0 ":irc.org 422 tester :MOTD File is missing"))
20
21((mode-user 1.2 "MODE tester +i")
22 (0.0 ":irc.org 221 tester +Zi")
23 (0.0 ":irc.org 306 tester :You have been marked as being away"))
24
25((join-foo 1.2 "JOIN #foo")
26 (0 ":tester!~tester@localhost JOIN #foo")
27 (0 ":irc.example.org 353 alice = #foo :+alice!~alice@example.com @%+bob!~bob@example.org")
28 (0 ":irc.example.org 366 alice #foo :End of NAMES list"))
29
30;; This would time out if the mode-foo's outgoing blocked (remove minus signs to see)
31((~join-bar 1.5 "JOIN #bar")
32 (0 ":tester!~tester@localhost JOIN #bar")
33 (0 ":irc.example.org 353 alice = #bar :+alice!~alice@example.com @%+bob!~bob@example.org")
34 (0 ":irc.example.org 366 alice #bar :End of NAMES list"))
35
36((mode-foo 1.2 "MODE #foo")
37 (0.0 ":irc.example.org 324 tester #foo +Cint")
38 (0.0 ":irc.example.org 329 tester #foo 1519850102")
39 (-0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defence, by mercy, 'tis most just.")
40 (-0.2 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: Grows, lives, and dies, in single blessedness.")
41 (-0.3 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: For these two hours, Rosalind, I will leave thee.")
42 (-0.4 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: By this hand, it will not kill a fly. But come, now I will be your Rosalind in a more coming-on disposition; and ask me what you will, I will grant it.")
43 (-0.5 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: That I must love a loathed enemy.")
44 (-0.6 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: As't please your lordship: I'll leave you.")
45 (-0.7 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: Then there is no true lover in the forest; else sighing every minute and groaning every hour would detect the lazy foot of Time as well as a clock.")
46 (-0.8 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: His discretion, I am sure, cannot carry his valour, for the goose carries not the fox. It is well: leave it to his discretion, and let us listen to the moon.")
47 (-0.9 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: As living here and you no use of him.")
48 (-1.0 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: If there be truth in sight, you are my Rosalind.")
49 (-1.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: That is another's lawful promis'd love.")
50 (-1.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :I am heard."))
51
52((mode-bar 1.5 "MODE #bar")
53 (0.0 ":irc.example.org 324 tester #bar +HMfnrt 50:5h :10:5")
54 (0.0 ":irc.example.org 329 tester #bar :1602642829")
55 (0.1 ":alice!~alice@example.com PRIVMSG #bar :hi 123"))
diff --git a/test/lisp/erc/resources/erc-d/resources/no-match.eld b/test/lisp/erc/resources/erc-d/resources/no-match.eld
new file mode 100644
index 00000000000..d147be1e084
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/no-match.eld
@@ -0,0 +1,32 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) "changeme"))
4((nick 0.2 "NICK tester"))
5
6((user 0.2 "USER user 0 * :tester")
7 (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester")
8 (0 ":irc.example.org 002 tester :Your host is irc.example.org")
9 (0 ":irc.example.org 003 tester :This server was created just now")
10 (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
12 " :are supported by this server")
13 (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
14 (0 ":irc.example.org 252 tester 0 :IRC Operators online")
15 (0 ":irc.example.org 253 tester 0 :unregistered connections")
16 (0 ":irc.example.org 254 tester 1 :channels formed")
17 (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers")
18 (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3")
19 (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3")
20 (0 ":irc.example.org 422 tester :MOTD File is missing"))
21
22((mode-user 1.2 "MODE tester +i")
23 (0 ":irc.example.org 221 tester +Zi")
24 (0 ":irc.example.org 306 tester :You have been marked as being away"))
25
26((join 1.2 "JOIN #chan")
27 (0 ":tester!~tester@localhost JOIN #chan")
28 (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org")
29 (0 ":irc.example.org 366 alice #chan :End of NAMES list"))
30
31((mode-chan 0.2 "MODE #chan")
32 (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey"))
diff --git a/test/lisp/erc/resources/erc-d/resources/no-pong.eld b/test/lisp/erc/resources/erc-d/resources/no-pong.eld
new file mode 100644
index 00000000000..30cd805d76c
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/no-pong.eld
@@ -0,0 +1,27 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) "changeme"))
4((nick 0.2 "NICK tester"))
5
6((~ping 1.2 "PING " nonce)
7 (0.1 ":irc.example.org PONG irc.example.com " echo))
8
9((user 0.2 "USER user 0 * :tester")
10 (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester")
11 (0 ":irc.example.org 002 tester :Your host is irc.example.org")
12 (0 ":irc.example.org 003 tester :This server was created just now")
13 (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv")
14 (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
15 " :are supported by this server")
16 (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
17 (0 ":irc.example.org 252 tester 0 :IRC Operators online")
18 (0 ":irc.example.org 253 tester 0 :unregistered connections")
19 (0 ":irc.example.org 254 tester 1 :channels formed")
20 (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers")
21 (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3")
22 (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3")
23 (0 ":irc.example.org 422 tester :MOTD File is missing"))
24
25((mode-user 1.2 "MODE tester +i")
26 (0 ":irc.example.org 221 tester +Zi")
27 (0 ":irc.example.org 306 tester :You have been marked as being away"))
diff --git a/test/lisp/erc/resources/erc-d/resources/nonstandard.eld b/test/lisp/erc/resources/erc-d/resources/nonstandard.eld
new file mode 100644
index 00000000000..c9cd608e6be
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/nonstandard.eld
@@ -0,0 +1,6 @@
1;;; -*- mode: lisp-data -*-
2((one 1 "ONE one"))
3((two 1 "TWO two"))
4((blank 1 ""))
5((one-space 1 " "))
6((two-spaces 1 " "))
diff --git a/test/lisp/erc/resources/erc-d/resources/proxy-barnet.eld b/test/lisp/erc/resources/erc-d/resources/proxy-barnet.eld
new file mode 100644
index 00000000000..e74d20d5b37
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/proxy-barnet.eld
@@ -0,0 +1,24 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) network ":changeme"))
4((nick 1.2 "NICK tester"))
5
6((user 1.2 "USER user 0 * :tester")
7 (0.001 ":" fqdn " 001 tester :Welcome to the BAR Network tester")
8 (0.002 ":" fqdn " 002 tester :Your host is " fqdn)
9 (0.003 ":" fqdn " 003 tester :This server was created just now")
10 (0.004 ":" fqdn " 004 tester " fqdn " BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0.005 ":" fqdn " 005 tester MODES NETWORK=" net " NICKLEN=32 PREFIX=(qaohv)~&@%+"
12 " :are supported by this server")
13 (0.006 ":" fqdn " 251 tester :There are 3 users and 0 invisible on 1 server(s)")
14 (0.007 ":" fqdn " 252 tester 0 :IRC Operators online")
15 (0.008 ":" fqdn " 253 tester 0 :unregistered connections")
16 (0.009 ":" fqdn " 254 tester 1 :channels formed")
17 (0.010 ":" fqdn " 255 tester :I have 3 clients and 0 servers")
18 (0.011 ":" fqdn " 265 tester 3 3 :Current local users 3, max 3")
19 (0.012 ":" fqdn " 266 tester 3 3 :Current global users 3, max 3")
20 (0.013 ":" fqdn " 422 tester :MOTD File is missing"))
21
22((mode-user 1.2 "MODE tester +i")
23 (0.014 ":" fqdn " 221 tester +Zi")
24 (0.015 ":" fqdn " 306 tester :You have been marked as being away"))
diff --git a/test/lisp/erc/resources/erc-d/resources/proxy-foonet.eld b/test/lisp/erc/resources/erc-d/resources/proxy-foonet.eld
new file mode 100644
index 00000000000..cc2e9d253c1
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/proxy-foonet.eld
@@ -0,0 +1,24 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) network ":changeme"))
4((nick 1.2 "NICK tester"))
5
6((user 2.2 "USER user 0 * :tester")
7 (0.015 ":" fqdn " 001 tester :Welcome to the FOO Network tester")
8 (0.014 ":" fqdn " 002 tester :Your host is " fqdn)
9 (0.013 ":" fqdn " 003 tester :This server was created just now")
10 (0.012 ":" fqdn " 004 tester " fqdn " BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0.011 ":" fqdn " 005 tester MODES NETWORK=" net " NICKLEN=32 PREFIX=(qaohv)~&@%+"
12 " :are supported by this server")
13 (0.010 ":" fqdn " 251 tester :There are 3 users and 0 invisible on 1 server(s)")
14 (0.009 ":" fqdn " 252 tester 0 :IRC Operators online")
15 (0.008 ":" fqdn " 253 tester 0 :unregistered connections")
16 (0.007 ":" fqdn " 254 tester 1 :channels formed")
17 (0.006 ":" fqdn " 255 tester :I have 3 clients and 0 servers")
18 (0.005 ":" fqdn " 265 tester 3 3 :Current local users 3, max 3")
19 (0.004 ":" fqdn " 266 tester 3 3 :Current global users 3, max 3")
20 (0.003 ":" fqdn " 422 tester :MOTD File is missing"))
21
22((mode-user 1.2 "MODE tester +i")
23 (0.002 ":" fqdn " 221 tester +Zi")
24 (0.001 ":" fqdn " 306 tester :You have been marked as being away"))
diff --git a/test/lisp/erc/resources/erc-d/resources/proxy-solo.eld b/test/lisp/erc/resources/erc-d/resources/proxy-solo.eld
new file mode 100644
index 00000000000..af216c80edc
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/proxy-solo.eld
@@ -0,0 +1,9 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) "changeme"))
4((nick 0.2 "NICK tester"))
5
6((user 0.2 "USER user 0 * :" (group (+ alpha)) eos)
7 (0 ":*status!znc@znc.in NOTICE " nick " :You have no networks configured."
8 " Use /znc AddNetwork <network> to add one.")
9 (0 ":irc.znc.in 001 " nick " :Welcome " nick "!"))
diff --git a/test/lisp/erc/resources/erc-d/resources/proxy-subprocess.el b/test/lisp/erc/resources/erc-d/resources/proxy-subprocess.el
new file mode 100644
index 00000000000..bb8869dff69
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/proxy-subprocess.el
@@ -0,0 +1,45 @@
1;;; proxy-subprocess.el --- Example setup file for erc-d -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
4;;
5;; This file is part of GNU Emacs.
6;;
7;; This program is free software: you can redistribute it and/or
8;; modify it under the terms of the GNU General Public License as
9;; published by the Free Software Foundation, either version 3 of the
10;; License, or (at your option) any later version.
11;;
12;; This program is distributed in the hope that it will be useful, but
13;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15;; General Public License for more details.
16;;
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see
19;; <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22;;; Code:
23
24(defvar erc-d-tmpl-vars)
25
26(setq erc-d-tmpl-vars
27
28 (list
29 (cons 'fqdn (lambda (helper)
30 (let ((name (funcall helper :dialog-name)))
31 (funcall helper :set
32 (if (eq name 'proxy-foonet)
33 "irc.foo.net"
34 "irc.bar.net")))))
35
36 (cons 'net (lambda (helper)
37 (let ((name (funcall helper :dialog-name)))
38 (funcall helper :set
39 (if (eq name 'proxy-foonet)
40 "FooNet"
41 "BarNet")))))
42
43 (cons 'network '(group (+ alpha)))))
44
45;;; proxy-subprocess.el ends here
diff --git a/test/lisp/erc/resources/erc-d/resources/timeout.eld b/test/lisp/erc/resources/erc-d/resources/timeout.eld
new file mode 100644
index 00000000000..9cfad4fa8cd
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/timeout.eld
@@ -0,0 +1,27 @@
1;;; -*- mode: lisp-data -*-
2
3((pass 10.0 "PASS " (? ?:) "changeme"))
4((nick 0.2 "NICK tester"))
5
6((user 0.2 "USER user 0 * :tester")
7 (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester")
8 (0 ":irc.example.org 002 tester :Your host is irc.example.org")
9 (0 ":irc.example.org 003 tester :This server was created just now")
10 (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv")
11 (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
12 " :are supported by this server")
13 (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
14 (0 ":irc.example.org 252 tester 0 :IRC Operators online")
15 (0 ":irc.example.org 253 tester 0 :unregistered connections")
16 (0 ":irc.example.org 254 tester 1 :channels formed")
17 (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers")
18 (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3")
19 (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3")
20 (0 ":irc.example.org 422 tester :MOTD File is missing"))
21
22((mode-user 1.2 "MODE tester +i")
23 (0 ":irc.example.org 221 tester +Zi")
24 (0 ":irc.example.org 306 tester :You have been marked as being away"))
25
26((mode 0.2 "MODE #chan")
27 (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey"))
diff --git a/test/lisp/erc/resources/erc-d/resources/unexpected.eld b/test/lisp/erc/resources/erc-d/resources/unexpected.eld
new file mode 100644
index 00000000000..ac0a8fecfa6
--- /dev/null
+++ b/test/lisp/erc/resources/erc-d/resources/unexpected.eld
@@ -0,0 +1,28 @@
1;;; -*- mode: lisp-data -*-
2((t 10.0 "PASS " (? ?:) "changeme"))
3((t 0.2 "NICK tester"))
4
5((t 0.2 "USER user 0 * :tester")
6 (0.0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester")
7 (0.0 ":irc.example.org 002 tester :Your host is irc.example.org")
8 (0.0 ":irc.example.org 003 tester :This server was created just now")
9 (0.0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv")
10 (0.0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+"
11 " :are supported by this server")
12 (0.0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)")
13 (0.0 ":irc.example.org 252 tester 0 :IRC Operators online")
14 (0.0 ":irc.example.org 253 tester 0 :unregistered connections")
15 (0.0 ":irc.example.org 254 tester 1 :channels formed")
16 (0.0 ":irc.example.org 255 tester :I have 3 clients and 0 servers")
17 (0.0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3")
18 (0.0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3")
19 (0.0 ":irc.example.org 422 tester :MOTD File is missing"))
20
21((mode-user 1.2 "MODE tester +i")
22 (0.0 ":irc.example.org 221 tester +Zi")
23
24 (0.0 ":irc.example.org 306 tester :You have been marked as being away")
25 (0.0 ":tester!~tester@localhost JOIN #chan")
26 (0.0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org")
27 (0.0 ":irc.example.org 366 alice #chan :End of NAMES list")
28 (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey"))