diff options
| author | F. Jason Park | 2021-05-13 03:33:33 -0700 |
|---|---|---|
| committer | F. Jason Park | 2022-06-30 15:03:26 -0700 |
| commit | 9be08ceb314888c7f86bddbec6490e7ead718a88 (patch) | |
| tree | 9f8dacca61ee9beba3fc7a726a923e1aba282516 | |
| parent | e958a2b726fdcb5a4f58169e6f4f384f5786f86a (diff) | |
| download | emacs-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.
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'. | ||
| 29 | When member `compat' is nil, it means the raw message was decoded as | ||
| 30 | UTF-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. | ||
| 72 | RAW must not have a leading \"@\" or a trailing space. The spec says | ||
| 73 | validation shouldn't be performed on keys and that undecodeable values | ||
| 74 | or ones with illegal (unescaped) chars may be dropped. This does not | ||
| 75 | respect 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. | ||
| 100 | With 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. | ||
| 52 | Use 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. | ||
| 61 | After CLEANUP, destroy any values in BINDINGS that remain bound to | ||
| 62 | buffers or processes. Sleep `erc-d-t-cleanup-sleep-secs' before | ||
| 63 | returning." | ||
| 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. | ||
| 90 | Or signal error with MSG after MAX-SECS. When MAX-SECS is negative, | ||
| 91 | signal if BODY is ever non-nil before MAX-SECS elapses. On success, | ||
| 92 | return BODY's value. | ||
| 93 | |||
| 94 | Note: this assumes BODY is waiting on a peer's output. It tends to | ||
| 95 | artificially accelerate consumption of all process output, which may not | ||
| 96 | be 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. | ||
| 120 | On 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. | ||
| 129 | With marker or number FROM, only consider the portion of the buffer from | ||
| 130 | that point forward. If TEXT is a cons, interpret it as an RX regular | ||
| 131 | expression. 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. | ||
| 151 | Assume new text is only inserted at or after `erc-insert-marker'. | ||
| 152 | |||
| 153 | The returned function works like `erc-d-t-search-for', but it never | ||
| 154 | revisits previously covered territory, and the optional fourth argument, | ||
| 155 | ON-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. | ||
| 549 | DIALOG may also be a list of dialogs. ERC-SERVER-BUFFER-VAR and | ||
| 550 | DUMB-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. | ||
| 594 | Do this for functions whose names appear in FUNC-SYMS. When running | ||
| 595 | advice code, add errors to list FOUND. Note: the teardown finalizer is | ||
| 596 | not added by default. Also, `erc-d-linger-secs' likely has to be | ||
| 597 | nonzero 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. | ||
| 1100 | These are steps shared by in-proc and subproc variants testing a | ||
| 1101 | bouncer-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. | ||
| 1185 | DIALOGS 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. | ||
| 139 | If DIALOG is a string, consider it a filename. Otherwise find a file | ||
| 140 | in `erc-d-u-canned-dialog-dir' with a base name matching the symbol's | ||
| 141 | name. | ||
| 142 | |||
| 143 | Return an iterator that yields exchanges, each one an iterator of spec | ||
| 144 | forms. The first is a so-called request spec and the rest are composed | ||
| 145 | of 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. | ||
| 163 | When NUM is a positive number, delay incoming requests by NUM more | ||
| 164 | seconds. If NUM is negative, raise insufficient incoming delays to at | ||
| 165 | least -NUM seconds. If NUM is a function, set each delay to whatever it | ||
| 166 | returns 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. | ||
| 174 | When 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. | ||
| 198 | Handle cases in which VAL is ([ARGLIST] RX-FORM) rather than just | ||
| 199 | RX-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. | ||
| 209 | The server process is usually deleted first, but we may want to examine | ||
| 210 | the 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. | ||
| 132 | Only 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\". | ||
| 136 | Possibly used by overriding handlers, like the one for PING, and/or | ||
| 137 | dialog 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. | ||
| 141 | For more granular control, use the provided LINGER `rx' variable (alone) | ||
| 142 | as 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. | ||
| 146 | Populate it when calling `erc-d-run', and the contents will be made | ||
| 147 | available to all client dialogs through the `erc-d-dialog' \"vars\" | ||
| 148 | field and (therefore) to all templates as variables when rendering. For | ||
| 149 | example, a key/value pair like (network . \"oftc\") will cause instances | ||
| 150 | of the (unquoted) symbol `network' to be replaced with \"oftc\" in the | ||
| 151 | rendered template string. | ||
| 152 | |||
| 153 | This list provides default template bindings common to all dialogs. | ||
| 154 | Each new client-connection process makes a shallow copy on init, but the | ||
| 155 | usual precautions apply when mutating member items. Within the span of | ||
| 156 | a dialog, updates not applicable to all exchanges should die with their | ||
| 157 | exchange. See `erc-d--render-entries' for details. In the unlikely | ||
| 158 | event 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. | ||
| 163 | This is meant to address edge cases for which `erc-d-tmpl-vars' comes up | ||
| 164 | short. These may include (1) needing access to the client process | ||
| 165 | itself and/or (2) adding or altering outgoing response templates before | ||
| 166 | rendering. Note that (2) requires using `erc-d-exchange-rebind' instead | ||
| 167 | of manipulating exchange bindings directly. | ||
| 168 | |||
| 169 | The 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' | ||
| 172 | object as the second). The handler runs just prior to sending the first | ||
| 173 | response.") | ||
| 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. | ||
| 183 | This is to allow for human interaction or a slow Emacs or CI runner. | ||
| 184 | The value is the number of seconds to extend all incoming spec timeouts | ||
| 185 | by on init. If the value is a negative number, it's negated and | ||
| 186 | interpreted as a lower bound to raise all incoming timeouts to. If the | ||
| 187 | value is a function, it should take an existing timeout in seconds and | ||
| 188 | return 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. | ||
| 272 | With 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. | ||
| 292 | PROCESS 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. | ||
| 335 | Return 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. | ||
| 383 | This 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. | ||
| 443 | PROCESS represents a client peer connection and STRING is a raw request | ||
| 444 | including 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. | ||
| 474 | Return the new server process immediately when `erc-d--in-process' is | ||
| 475 | non-nil. Otherwise, serve forever. PLIST becomes the plist of the | ||
| 476 | server process and is used to initialize the plists of connection | ||
| 477 | processes. 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. | ||
| 504 | Arrange for FUNC to be called with the args it expects based on | ||
| 505 | the 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 | |||
| 532 | Bind symbol KEY to VAL, replacing whatever existed before, which may | ||
| 533 | have been a function. A third, optional argument, if present and | ||
| 534 | non-nil, results in the DIALOG's bindings for all EXCHANGEs adopting | ||
| 535 | this 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 | ||
| 537 | function `concat'. | ||
| 538 | |||
| 539 | DIALOG and EXCHANGE are the current `erc-d-dialog' and `erc-d-exchange' | ||
| 540 | objects 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. | ||
| 552 | MATCH-NUMBER is the match group number. TAG, if provided, means the | ||
| 553 | exchange 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. | ||
| 564 | This is a utility passed as the first argument to all template | ||
| 565 | functions. DIALOG and EXCHANGE are pre-applied. A few pseudo | ||
| 566 | commands, like `:request', are provided for convenience so that | ||
| 567 | the caller's definition doesn't have to include this file. The | ||
| 568 | rest are access and mutation utilities, such as `:set', which | ||
| 569 | assigns 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. | ||
| 606 | When an entry's CAR is an arbitrary symbol, yield that back first, and | ||
| 607 | consider the entry an \"incoming\" entry. Then, regardless of the | ||
| 608 | entry's type (incoming or outgoing), yield back the next element, which | ||
| 609 | should be a number representing either a timeout (incoming) or a | ||
| 610 | delay (outgoing). After that, yield a rendered template (outgoing) or a | ||
| 611 | regular expression (incoming); both should be treated as immutable. | ||
| 612 | |||
| 613 | When evaluating a template, bind the keys in the alist stored in the | ||
| 614 | dialog'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 | ||
| 616 | value, which can be any valid `rx' form (see Info node `(elisp) | ||
| 617 | Extending Rx'). Do the same when an entry is outgoing, but expect a | ||
| 618 | value's form to be (anything that evaluates to) something acceptable by | ||
| 619 | `concat' or, alternatively, a function that returns a string or nil. | ||
| 620 | |||
| 621 | Repeat the last two steps for the remaining entries, all of which are | ||
| 622 | assumed to be outgoing. That is, continue yielding a timeout/delay and | ||
| 623 | a rendered string for each entry, and yield nil when exhausted. | ||
| 624 | |||
| 625 | Once again, for an incoming entry, the yielded string is a regexp to be | ||
| 626 | matched against the raw request. For outgoing, it's the final response, | ||
| 627 | ready to be sent out (after adding the appropriate line ending). | ||
| 628 | |||
| 629 | To help with testing, bindings are not automatically created from | ||
| 630 | DIALOG's \"vars\" alist when this function is invoked. But this can be | ||
| 631 | forced by sending a non-nil YIELD-RESULT into the generator on the | ||
| 632 | second \"next\" invocation of a given iteration. This clobbers any | ||
| 633 | temporary bindings that don't exist in the DIALOG's `vars' alist, such | ||
| 634 | as those added via `erc-d-exchange-rebind' (unless \"exported\"). | ||
| 635 | |||
| 636 | As noted earlier, template symbols can be bound to functions. When | ||
| 637 | called during rendering, the match data from the current (matched) | ||
| 638 | request is accessible by calling the function `match-data'. | ||
| 639 | |||
| 640 | A function may ask for up to two required args, which are provided as | ||
| 641 | needed. When applicable, the first required arg is a `funcall'-able | ||
| 642 | helper that accepts various keyword-based commands, like :rebind, and a | ||
| 643 | variable number of args. See `erc-d-exchange-multi' for details. When | ||
| 644 | specified, the second required arg is the current `erc-d-exchange' | ||
| 645 | object, which has among its members its owning `erc-d-dialog' object. | ||
| 646 | This should suffice as a safety valve for any corner-case needs. | ||
| 647 | Non-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. | ||
| 681 | Allow the first handler in `erc-d-match-handlers' whose key matches TAG | ||
| 682 | to 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. | ||
| 691 | Assume the next spec is outgoing. If its delay value is zero, render | ||
| 692 | the template and send the resulting message straight away. Do the same | ||
| 693 | when DELAY is negative, only arrange for its message to be sent (abs | ||
| 694 | DELAY) seconds later, and then keep on processing. If DELAY is | ||
| 695 | positive, 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. | ||
| 770 | Upon encountering a mismatch, return an error of the form (ERROR-SYMBOL | ||
| 771 | DATA). 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. | ||
| 790 | That is, return nil when RING is empty or when it only has exchanges | ||
| 791 | with 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. | ||
| 811 | Replenish exchanges from reader and insert them into the pool of | ||
| 812 | expected matches, as produced. Return a symbol indicating session | ||
| 813 | status: 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. | ||
| 828 | Do this for some previously matched EXCHANGE in DIALOG based on CMD, a | ||
| 829 | symbol. As a side effect, maybe schedule the resumption of the main | ||
| 830 | loop 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. | ||
| 856 | Have it ingest internal control commands (lowercase symbols) and yield | ||
| 857 | back 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. | ||
| 887 | By default, defer to this dialog's `erc-d--command-handle-all' instance, | ||
| 888 | which 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. | ||
| 912 | Pass HOST and SERVICE directly to `make-network-process'. When present, | ||
| 913 | use string SERVER-NAME for the server-process name as well as that of | ||
| 914 | its buffer (w. surrounding asterisks). When absent, do the same with | ||
| 915 | `erc-d-server-name'. When running \"in process,\" return the server | ||
| 916 | process, otherwise sleep for the duration of the server process. | ||
| 917 | |||
| 918 | A dialog must be a symbol matching the base name of a dialog file in | ||
| 919 | `erc-d-u-canned-dialog-dir'. | ||
| 920 | |||
| 921 | The variable `erc-d-tmpl-vars' determines the common members of the | ||
| 922 | `erc-d--render-entries' ENTRIES param. Variables `erc-d-server-fqdn' | ||
| 923 | and `erc-d-linger-secs' determine the `erc-d-dialog' items | ||
| 924 | `:server-fqdn' and `:linger-secs' for all client processes. | ||
| 925 | |||
| 926 | The variable `erc-d-tmpl-vars' can be used to initialize the | ||
| 927 | process'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. | ||
| 948 | Although not autoloaded, this function is meant to be summoned via the | ||
| 949 | Emacs -f flag while starting a batch session. It prints incoming and | ||
| 950 | outgoing messages to standard out. | ||
| 951 | |||
| 952 | The main options are --host HOST and --port PORT, which default to | ||
| 953 | localhost and auto, respectively. The args are the dialogs to run. | ||
| 954 | Unlike with `erc-d-run', dialogs here *must* be files, meaning Lisp-Data | ||
| 955 | files adhering to the required format. (These consist of \"specs\" | ||
| 956 | detailing timing and template info; see commentary for specifics.) | ||
| 957 | |||
| 958 | An optional --add-time N option can also be passed to hike up timeouts | ||
| 959 | by 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 | |||
| 969 | from a Makefile or manually with \\<global-map>\\[compile]. And then in | ||
| 970 | another 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 | |||
| 976 | Use `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")) | ||