From 9be08ceb314888c7f86bddbec6490e7ead718a88 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 13 May 2021 03:33:33 -0700 Subject: 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. --- test/lisp/erc/resources/erc-d/erc-d-i.el | 126 ++ test/lisp/erc/resources/erc-d/erc-d-t.el | 170 +++ test/lisp/erc/resources/erc-d/erc-d-tests.el | 1346 ++++++++++++++++++++ test/lisp/erc/resources/erc-d/erc-d-u.el | 213 ++++ test/lisp/erc/resources/erc-d/erc-d.el | 997 +++++++++++++++ test/lisp/erc/resources/erc-d/resources/basic.eld | 32 + .../erc/resources/erc-d/resources/depleted.eld | 12 + test/lisp/erc/resources/erc-d/resources/drop-a.eld | 4 + test/lisp/erc/resources/erc-d/resources/drop-b.eld | 4 + .../resources/erc-d/resources/dynamic-barnet.eld | 33 + .../resources/erc-d/resources/dynamic-foonet.eld | 32 + .../erc/resources/erc-d/resources/dynamic-stub.eld | 4 + .../lisp/erc/resources/erc-d/resources/dynamic.eld | 30 + test/lisp/erc/resources/erc-d/resources/eof.eld | 33 + test/lisp/erc/resources/erc-d/resources/fuzzy.eld | 42 + .../erc/resources/erc-d/resources/incremental.eld | 43 + .../resources/erc-d/resources/irc-parser-tests.eld | 380 ++++++ .../resources/erc-d/resources/linger-multi-a.eld | 3 + .../resources/erc-d/resources/linger-multi-b.eld | 3 + test/lisp/erc/resources/erc-d/resources/linger.eld | 33 + .../erc/resources/erc-d/resources/no-block.eld | 55 + .../erc/resources/erc-d/resources/no-match.eld | 32 + .../lisp/erc/resources/erc-d/resources/no-pong.eld | 27 + .../erc/resources/erc-d/resources/nonstandard.eld | 6 + .../erc/resources/erc-d/resources/proxy-barnet.eld | 24 + .../erc/resources/erc-d/resources/proxy-foonet.eld | 24 + .../erc/resources/erc-d/resources/proxy-solo.eld | 9 + .../resources/erc-d/resources/proxy-subprocess.el | 45 + .../lisp/erc/resources/erc-d/resources/timeout.eld | 27 + .../erc/resources/erc-d/resources/unexpected.eld | 28 + 30 files changed, 3817 insertions(+) create mode 100644 test/lisp/erc/resources/erc-d/erc-d-i.el create mode 100644 test/lisp/erc/resources/erc-d/erc-d-t.el create mode 100644 test/lisp/erc/resources/erc-d/erc-d-tests.el create mode 100644 test/lisp/erc/resources/erc-d/erc-d-u.el create mode 100644 test/lisp/erc/resources/erc-d/erc-d.el create mode 100644 test/lisp/erc/resources/erc-d/resources/basic.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/depleted.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/drop-a.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/drop-b.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/dynamic-barnet.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/dynamic-foonet.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/dynamic-stub.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/dynamic.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/eof.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/fuzzy.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/incremental.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/irc-parser-tests.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/linger-multi-a.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/linger-multi-b.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/linger.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/no-block.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/no-match.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/no-pong.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/nonstandard.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/proxy-barnet.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/proxy-foonet.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/proxy-solo.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/proxy-subprocess.el create mode 100644 test/lisp/erc/resources/erc-d/resources/timeout.eld create mode 100644 test/lisp/erc/resources/erc-d/resources/unexpected.eld (limited to 'test/lisp/erc/resources/erc-d') 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 @@ +;;; erc-d-i.el --- IRC helpers for ERC test server -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see +;; . + +;;; Commentary: + +;;; Code: + +(require 'cl-lib) + +(cl-defstruct (erc-d-i-message (:conc-name erc-d-i-message.)) + "Identical to `erc-response'. +When member `compat' is nil, it means the raw message was decoded as +UTF-8 text before parsing, which is nonstandard." + (unparsed "" :type string) + (sender "" :type string) + (command "" :type string) + (command-args nil :type (list-of string)) + (contents "" :type string) + (tags nil :type (list-of (cons symbol string))) + (compat t :type boolean)) + +(defconst erc-d-i--tag-escapes + '((";" . "\\:") (" " . "\\s") ("\\" . "\\\\") ("\r" . "\\r") ("\n" . "\\n"))) + +;; XXX these are not mirror inverses; unescaping may degenerate +;; original by dropping stranded/misplaced backslashes. + +(defconst erc-d-i--tag-escaped-regexp (rx (or ?\; ?\ ?\\ ?\r ?\n))) + +(defconst erc-d-i--tag-unescaped-regexp + (rx (or "\\:" "\\s" "\\\\" "\\r" "\\n" + (seq "\\" (or string-end (not (or ":" "n" "r" "\\"))))))) + +(defun erc-d-i--unescape-tag-value (str) + "Undo substitution of char placeholders in raw tag value STR." + (replace-regexp-in-string erc-d-i--tag-unescaped-regexp + (lambda (s) + (or (car (rassoc s erc-d-i--tag-escapes)) + (substring s 1))) + str t t)) + +(defun erc-d-i--escape-tag-value (str) + "Swap out banned chars in tag value STR with message representation." + (replace-regexp-in-string erc-d-i--tag-escaped-regexp + (lambda (s) + (cdr (assoc s erc-d-i--tag-escapes))) + str t t)) + +(defconst erc-d-i--invalid-tag-regexp (rx (any "\0\7\r\n; "))) + +;; This is `erc-v3-message-tags' with fatal errors. + +(defun erc-d-i--validate-tags (raw) + "Validate tags portion of some RAW incoming message. +RAW must not have a leading \"@\" or a trailing space. The spec says +validation shouldn't be performed on keys and that undecodeable values +or ones with illegal (unescaped) chars may be dropped. This does not +respect any of that. Its purpose is to catch bad input created by us." + (unless (> 4094 (string-bytes raw)) + ;; 417 ERR_INPUTTOOLONG Input line was too long + (error "Message tags exceed 4094 bytes: %S" raw)) + (let (tags + (tag-strings (split-string raw ";"))) + (dolist (s tag-strings (nreverse tags)) + (let* ((m (if (>= emacs-major-version 28) + (string-search "=" s) + (string-match-p "=" s))) + (key (if m (substring s 0 m) s)) + (val (when-let* (m ; check first, like (m), but shadow + (v (substring s (1+ m))) + ((not (string-equal v "")))) + (when (string-match-p erc-d-i--invalid-tag-regexp v) + (error "Bad tag: %s" s)) + (thread-first v + (decode-coding-string 'utf-8 t) + (erc-d-i--unescape-tag-value))))) + (when (string-empty-p key) + (error "Tag missing key: %S" s)) + (setf (alist-get (intern key) tags) val))))) + +(defun erc-d-i--parse-message (s &optional decode) + "Parse string S into `erc-d-i-message' object. +With DECODE, decode as UTF-8 text." + (when (string-suffix-p "\r\n" s) + (error "Unstripped message encountered")) + (when decode + (setq s (decode-coding-string s 'utf-8 t))) + (let ((mes (make-erc-d-i-message :unparsed s :compat (not decode))) + tokens) + (when-let* (((not (string-empty-p s))) + ((eq ?@ (aref s 0))) + (m (string-match " " s)) + (u (substring s 1 m))) + (setf (erc-d-i-message.tags mes) (erc-d-i--validate-tags u) + s (substring s (1+ m)))) + (if-let* ((m (string-match " :" s)) + (other-toks (split-string (substring s 0 m) " " t)) + (rest (substring s (+ 2 m)))) + (setf (erc-d-i-message.contents mes) rest + tokens (nconc other-toks (list rest))) + (setq tokens (split-string s " " t " "))) + (when (and tokens (eq ?: (aref (car tokens) 0))) + (setf (erc-d-i-message.sender mes) (substring (pop tokens) 1))) + (setf (erc-d-i-message.command mes) (or (pop tokens) "") + (erc-d-i-message.command-args mes) tokens) + mes)) + +(provide 'erc-d-i) +;;; 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 @@ +;;; erc-d-t.el --- ERT helpers for ERC test server -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see +;; . + +;;; Commentary: + +;;; Code: +(eval-and-compile + (let* ((d (file-name-directory (or (macroexp-file-name) buffer-file-name))) + (load-path (cons (directory-file-name d) load-path))) + (require 'erc-d-u))) + +(require 'ert) + +(defun erc-d-t-kill-related-buffers () + "Kill all erc- or erc-d- related buffers." + (let (buflist) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (or erc-d-u--process-buffer + (derived-mode-p 'erc-mode)) + (push buf buflist)))) + (dolist (buf buflist) + (when (and (boundp 'erc-server-flood-timer) + (timerp erc-server-flood-timer)) + (cancel-timer erc-server-flood-timer)) + (when-let ((proc (get-buffer-process buf))) + (delete-process proc)) + (when (buffer-live-p buf) + (kill-buffer buf)))) + (while (when-let ((buf (pop erc-d-u--canned-buffers))) + (kill-buffer buf)))) + +(defun erc-d-t-silence-around (orig &rest args) + "Run ORIG function with ARGS silently. +Use this on `erc-handle-login' and `erc-server-connect'." + (let ((inhibit-message t)) + (apply orig args))) + +(defvar erc-d-t-cleanup-sleep-secs 0.1) + +(defmacro erc-d-t-with-cleanup (bindings cleanup &rest body) + "Execute BODY and run CLEANUP form regardless of outcome. +`let*'-bind BINDINGS and make them available in BODY and CLEANUP. +After CLEANUP, destroy any values in BINDINGS that remain bound to +buffers or processes. Sleep `erc-d-t-cleanup-sleep-secs' before +returning." + (declare (indent 2)) + `(let* ,bindings + (unwind-protect + (progn ,@body) + ,cleanup + (when noninteractive + (let (bufs procs) + (dolist (o (list ,@(mapcar (lambda (b) (or (car-safe b) b)) + bindings))) + (when (bufferp o) + (push o bufs)) + (when (processp o) + (push o procs))) + (dolist (proc procs) + (delete-process proc) + (when-let ((buf (process-buffer proc))) + (push buf bufs))) + (dolist (buf bufs) + (when-let ((proc (get-buffer-process buf))) + (delete-process proc)) + (when (bufferp buf) + (ignore-errors (kill-buffer buf))))) + (sleep-for erc-d-t-cleanup-sleep-secs))))) + +(defmacro erc-d-t-wait-for (max-secs msg &rest body) + "Wait for BODY to become non-nil. +Or signal error with MSG after MAX-SECS. When MAX-SECS is negative, +signal if BODY is ever non-nil before MAX-SECS elapses. On success, +return BODY's value. + +Note: this assumes BODY is waiting on a peer's output. It tends to +artificially accelerate consumption of all process output, which may not +be desirable." + (declare (indent 2)) + (unless (or (stringp msg) (memq (car-safe msg) '(format concat))) + (push msg body) + (setq msg (prin1-to-string body))) + (let ((inverted (make-symbol "inverted")) + (time-out (make-symbol "time-out")) + (result (make-symbol "result"))) + `(ert-info ((concat "Awaiting: " ,msg)) + (let ((,time-out (abs ,max-secs)) + (,inverted (< ,max-secs 0)) + (,result ',result)) + (with-timeout (,time-out (if ,inverted + (setq ,inverted nil) + (error "Failed awaiting: %s" ,msg))) + (while (not (setq ,result (progn ,@body))) + (when (and (accept-process-output nil 0.1) (not noninteractive)) + (redisplay)))) + (when ,inverted + (error "Failed awaiting: %s" ,msg)) + ,result)))) + +(defmacro erc-d-t-ensure-for (max-secs msg &rest body) + "Ensure BODY remains non-nil for MAX-SECS. +On failure, emit MSG." + (declare (indent 2)) + (unless (or (stringp msg) (memq (car-safe msg) '(format concat))) + (push msg body) + (setq msg (prin1-to-string body))) + `(erc-d-t-wait-for (- (abs ,max-secs)) ,msg (not (progn ,@body)))) + +(defun erc-d-t-search-for (timeout text &optional from on-success) + "Wait for TEXT to appear in current buffer before TIMEOUT secs. +With marker or number FROM, only consider the portion of the buffer from +that point forward. If TEXT is a cons, interpret it as an RX regular +expression. If ON-SUCCESS is a function, call it when TEXT is found." + (save-restriction + (widen) + (let* ((rxp (consp text)) + (fun (if rxp #'search-forward-regexp #'search-forward)) + (pat (if rxp (rx-to-string text) text)) + res) + (erc-d-t-wait-for timeout (format "string: %s" text) + (goto-char (or from (point-min))) + (setq res (funcall fun pat nil t)) + (if (and on-success res) + (funcall on-success) + res))))) + +(defun erc-d-t-absent-for (timeout text &optional from on-success) + "Assert TEXT doesn't appear in current buffer for TIMEOUT secs." + (erc-d-t-search-for (- (abs timeout)) text from on-success)) + +(defun erc-d-t-make-expecter () + "Return function to search for new output in buffer. +Assume new text is only inserted at or after `erc-insert-marker'. + +The returned function works like `erc-d-t-search-for', but it never +revisits previously covered territory, and the optional fourth argument, +ON-SUCCESS, is nonexistent. To reset, specify a FROM argument." + (let (positions) + (lambda (timeout text &optional reset-from) + (let* ((pos (cdr (assq (current-buffer) positions))) + (cb (lambda () + (unless pos + (push (cons (current-buffer) (setq pos (make-marker))) + positions)) + (marker-position + (set-marker pos (min (point) (1- (point-max)))))))) + (when reset-from + (set-marker pos reset-from)) + (erc-d-t-search-for timeout text pos cb))))) + +(provide 'erc-d-t) +;;; 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 @@ +;;; erc-d-tests.el --- tests for erc-d -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see +;; . + +;;; Commentary: + +;;; Code: +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (expand-file-name ".." (ert-resource-directory)) + load-path))) + (require 'erc-d) + (require 'erc-d-t))) + +(require 'erc) + +;; Temporary kludge to silence warning +(put 'erc-parse-tags 'erc-v3-warned-p t) + +(ert-deftest erc-d-u--canned-load-dialog--basic () + (should-not (get-buffer "basic.eld")) + (should-not erc-d-u--canned-buffers) + (let* ((exes (erc-d-u--canned-load-dialog 'basic)) + (reap (lambda () + (cl-loop with e = (erc-d-u--read-dialog exes) + for s = (erc-d-u--read-exchange e) + while s collect s)))) + (should (get-buffer "basic.eld")) + (should (memq (get-buffer "basic.eld") erc-d-u--canned-buffers)) + (should (equal (funcall reap) '((pass 10.0 "PASS " (? ?:) "changeme")))) + (should (equal (funcall reap) '((nick 0.2 "NICK tester")))) + (let ((r (funcall reap))) + (should (equal (car r) '(user 0.2 "USER user 0 * :tester"))) + (should (equal + (car (last r)) + '(0 ":irc.example.org 422 tester :MOTD File is missing")))) + (should (equal (car (funcall reap)) '(mode-user 5 "MODE tester +i"))) + (should (equal (funcall reap) + '((mode-chan 1.2 "MODE #chan") + (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey")))) + ;; See `define-error' site for `iter-end-of-sequence' + (ert-info ("EOB detected") (should-not (erc-d-u--read-dialog exes)))) + (should-not (get-buffer "basic.eld")) + (should-not erc-d-u--canned-buffers)) + +(defun erc-d-tests--make-hunk-reader (hunks) + (let ((p (erc-d-u--read-dialog hunks))) + (lambda () (erc-d-u--read-exchange p)))) + +;; Fuzzies need to be able to access any non-exhausted genny. +(ert-deftest erc-d-u--canned-load-dialog--intermingled () + (should-not (get-buffer "basic.eld")) + (should-not erc-d-u--canned-buffers) + (let* ((exes (erc-d-u--canned-load-dialog 'basic)) + (pass (erc-d-tests--make-hunk-reader exes)) + (nick (erc-d-tests--make-hunk-reader exes)) + (user (erc-d-tests--make-hunk-reader exes)) + (modu (erc-d-tests--make-hunk-reader exes)) + (modc (erc-d-tests--make-hunk-reader exes))) + + (should (equal (funcall user) '(user 0.2 "USER user 0 * :tester"))) + (should (equal (funcall modu) '(mode-user 5 "MODE tester +i"))) + (should (equal (funcall modc) '(mode-chan 1.2 "MODE #chan"))) + + (cl-loop repeat 8 do (funcall user)) ; skip a few + (should (equal (funcall user) + '(0 ":irc.example.org 254 tester 1 :channels formed"))) + (should (equal (funcall modu) + '(0 ":irc.example.org 221 tester +Zi"))) + (should (equal (cl-loop for s = (funcall modc) while s collect s) ; done + '((0.1 ":bob!~bob@example.org PRIVMSG #chan :hey")))) + + (cl-loop repeat 3 do (funcall user)) + (cl-loop repeat 3 do (funcall modu)) + + (ert-info ("Change up the order") + (should + (equal (funcall modu) + '(0 ":irc.example.org 366 alice #chan :End of NAMES list"))) + (should + (equal (funcall user) + '(0 ":irc.example.org 422 tester :MOTD File is missing")))) + + ;; Exhaust these + (should (equal (cl-loop for s = (funcall pass) while s collect s) ; done + '((pass 10.0 "PASS " (? ?:) "changeme")))) + (should (equal (cl-loop for s = (funcall nick) while s collect s) ; done + '((nick 0.2 "NICK tester")))) + + (ert-info ("End of file but no teardown because hunks outstanding") + (should-not (erc-d-u--read-dialog exes)) + (should (get-buffer "basic.eld"))) + + ;; Finish + (should-not (funcall user)) + (should-not (funcall modu))) + + (should-not (get-buffer "basic.eld")) + (should-not erc-d-u--canned-buffers)) + +;; This indirectly tests `erc-d-u--canned-read' cleanup/teardown + +(ert-deftest erc-d-u--rewrite-for-slow-mo () + (should-not (get-buffer "basic.eld")) + (should-not (get-buffer "basic.eld<2>")) + (should-not (get-buffer "basic.eld<3>")) + (should-not erc-d-u--canned-buffers) + (let ((exes (erc-d-u--canned-load-dialog 'basic)) + (exes-lower (erc-d-u--canned-load-dialog 'basic)) + (exes-custom (erc-d-u--canned-load-dialog 'basic)) + (reap (lambda (e) (cl-loop with p = (erc-d-u--read-dialog e) + for s = (erc-d-u--read-exchange p) + while s collect s)))) + (should (get-buffer "basic.eld")) + (should (get-buffer "basic.eld<2>")) + (should (get-buffer "basic.eld<3>")) + (should (equal (list (get-buffer "basic.eld<3>") + (get-buffer "basic.eld<2>") + (get-buffer "basic.eld")) + erc-d-u--canned-buffers)) + + (ert-info ("Rewrite for slowmo basic") + (setq exes (erc-d-u--rewrite-for-slow-mo 10 exes)) + (should (equal (funcall reap exes) + '((pass 20.0 "PASS " (? ?:) "changeme")))) + (should (equal (funcall reap exes) + '((nick 10.2 "NICK tester")))) + (let ((r (funcall reap exes))) + (should (equal (car r) '(user 10.2 "USER user 0 * :tester"))) + (should (equal + (car (last r)) + '(0 ":irc.example.org 422 tester :MOTD File is missing")))) + (should (equal (car (funcall reap exes)) + '(mode-user 15 "MODE tester +i"))) + (should (equal (car (funcall reap exes)) + '(mode-chan 11.2 "MODE #chan"))) + (should-not (erc-d-u--read-dialog exes))) + + (ert-info ("Rewrite for slowmo bounded") + (setq exes-lower (erc-d-u--rewrite-for-slow-mo -5 exes-lower)) + (should (equal (funcall reap exes-lower) + '((pass 10.0 "PASS " (? ?:) "changeme")))) + (should (equal (funcall reap exes-lower) + '((nick 5 "NICK tester")))) + (should (equal (car (funcall reap exes-lower)) + '(user 5 "USER user 0 * :tester"))) + (should (equal (car (funcall reap exes-lower)) + '(mode-user 5 "MODE tester +i"))) + (should (equal (car (funcall reap exes-lower)) + '(mode-chan 5 "MODE #chan"))) + (should-not (erc-d-u--read-dialog exes-lower))) + + (ert-info ("Rewrite for slowmo custom") + (setq exes-custom (erc-d-u--rewrite-for-slow-mo + (lambda (n) (* 2 n)) exes-custom)) + (should (equal (funcall reap exes-custom) + '((pass 20.0 "PASS " (? ?:) "changeme")))) + (should (equal (funcall reap exes-custom) + '((nick 0.4 "NICK tester")))) + (should (equal (car (funcall reap exes-custom)) + '(user 0.4 "USER user 0 * :tester"))) + (should (equal (car (funcall reap exes-custom)) + '(mode-user 10 "MODE tester +i"))) + (should (equal (car (funcall reap exes-custom)) + '(mode-chan 2.4 "MODE #chan"))) + (should-not (erc-d-u--read-dialog exes-custom)))) + + (should-not (get-buffer "basic.eld")) + (should-not (get-buffer "basic.eld<2>")) + (should-not (get-buffer "basic.eld<3>")) + (should-not erc-d-u--canned-buffers)) + +(ert-deftest erc-d--active-ex-p () + (let ((ring (make-ring 5))) + (ert-info ("Empty ring returns nil for not active") + (should-not (erc-d--active-ex-p ring))) + (ert-info ("One fuzzy member returns nil for not active") + (ring-insert ring (make-erc-d-exchange :tag '~foo)) + (should-not (erc-d--active-ex-p ring))) + (ert-info ("One active member returns t for active") + (ring-insert-at-beginning ring (make-erc-d-exchange :tag 'bar)) + (should (erc-d--active-ex-p ring))))) + +(defun erc-d-tests--parse-message-upstream (raw) + "Hack shim for parsing RAW line recvd from peer." + (cl-letf (((symbol-function #'erc-handle-parsed-server-response) + (lambda (_ p) p))) + (let ((erc-active-buffer nil)) + (erc-parse-server-response nil raw)))) + +(ert-deftest erc-d-i--validate-tags () + (should (erc-d-i--validate-tags + (concat "batch=4cc99692bf24a4bec4aa03da437364f5;" + "time=2021-01-04T00:32:13.839Z"))) + (should (erc-d-i--validate-tags "+foo=bar;baz=spam")) + (should (erc-d-i--validate-tags "foo=\\:ok;baz=\\s")) + (should (erc-d-i--validate-tags "foo=\303\247edilla")) + (should (erc-d-i--validate-tags "foo=\\")) + (should (erc-d-i--validate-tags "foo=bar\\baz")) + (should-error (erc-d-i--validate-tags "foo=\\\\;baz=\\\r\\\n")) + (should-error (erc-d-i--validate-tags "foo=\n")) + (should-error (erc-d-i--validate-tags "foo=\0ok")) + (should-error (erc-d-i--validate-tags "foo=bar baz")) + (should-error (erc-d-i--validate-tags "foo=bar\r")) + (should-error (erc-d-i--validate-tags "foo=bar;"))) + +(ert-deftest erc-d-i--parse-message () + (let* ((raw (concat "@time=2020-11-23T09:10:33.088Z " + ":tilde.chat BATCH +1 chathistory :#meta")) + (upstream (erc-d-tests--parse-message-upstream raw)) + (ours (erc-d-i--parse-message raw))) + + (ert-info ("Baseline upstream") + (should (equal (erc-response.unparsed upstream) raw)) + (should (equal (erc-response.sender upstream) "tilde.chat")) + (should (equal (erc-response.command upstream) "BATCH")) + (should (equal (erc-response.command-args upstream) + '("+1" "chathistory" "#meta"))) + (should (equal (erc-response.contents upstream) "#meta"))) + + (ert-info ("Ours my not compare cl-equalp but is otherwise the same") + (should (equal (erc-d-i-message.unparsed ours) raw)) + (should (equal (erc-d-i-message.sender ours) "tilde.chat")) + (should (equal (erc-d-i-message.command ours) "BATCH")) + (should (equal (erc-d-i-message.command-args ours) + '("+1" "chathistory" "#meta"))) + (should (equal (erc-d-i-message.contents ours) "#meta")) + (should (equal (erc-d-i-message.tags ours) + '((time . "2020-11-23T09:10:33.088Z"))))) + + (ert-info ("No compat decodes the whole message as utf-8") + (setq ours (erc-d-i--parse-message + "@foo=\303\247edilla TAGMSG #ch\303\240n" + 'decode)) + (should-not (erc-d-i-message.compat ours)) + (should (equal (erc-d-i-message.command-args ours) '("#chàn"))) + (should (equal (erc-d-i-message.contents ours) "")) + (should (equal (erc-d-i-message.tags ours) '((foo . "çedilla"))))))) + +(ert-deftest erc-d-i--unescape-tag-value () + (should (equal (erc-d-i--unescape-tag-value + "\\sabc\\sdef\\s\\sxyz\\s") + " abc def xyz ")) + (should (equal (erc-d-i--unescape-tag-value + "\\\\abc\\\\def\\\\\\\\xyz\\\\") + "\\abc\\def\\\\xyz\\")) + (should (equal (erc-d-i--unescape-tag-value "a\\bc") "abc")) + (should (equal (erc-d-i--unescape-tag-value + "\\\\abc\\\\def\\\\\\\\xyz\\") + "\\abc\\def\\\\xyz")) + (should (equal (erc-d-i--unescape-tag-value "a\\:b\\r\\nc\\sd") + "a;b\r\nc d"))) + +(ert-deftest erc-d-i--escape-tag-value () + (should (equal (erc-d-i--escape-tag-value " abc def xyz ") + "\\sabc\\sdef\\s\\sxyz\\s")) + (should (equal (erc-d-i--escape-tag-value "\\abc\\def\\\\xyz\\") + "\\\\abc\\\\def\\\\\\\\xyz\\\\")) + (should (equal (erc-d-i--escape-tag-value "a;b\r\nc d") + "a\\:b\\r\\nc\\sd"))) + +;; TODO add tests for msg-join, mask-match, userhost-split, +;; validate-hostname + +(ert-deftest erc-d-i--parse-message--irc-parser-tests () + (let* ((data (with-temp-buffer + (insert-file-contents + (expand-file-name "irc-parser-tests.eld" + (ert-resource-directory))) + (read (current-buffer)))) + (tests (assoc-default 'tests (assoc-default 'msg-split data))) + input atoms m ours) + (dolist (test tests) + (setq input (assoc-default 'input test) + atoms (assoc-default 'atoms test) + m (erc-d-i--parse-message input)) + (ert-info ("Parses tags correctly") + (setq ours (erc-d-i-message.tags m)) + (if-let ((tags (assoc-default 'tags atoms))) + (pcase-dolist (`(,key . ,value) ours) + (should (string= (cdr (assq key tags)) (or value "")))) + (should-not ours))) + (ert-info ("Parses verbs correctly") + (setq ours (erc-d-i-message.command m)) + (if-let ((verbs (assoc-default 'verb atoms))) + (should (string= (downcase verbs) (downcase ours))) + (should (string-empty-p ours)))) + (ert-info ("Parses sources correctly") + (setq ours (erc-d-i-message.sender m)) + (if-let ((source (assoc-default 'source atoms))) + (should (string= source ours)) + (should (string-empty-p ours)))) + (ert-info ("Parses params correctly") + (setq ours (erc-d-i-message.command-args m)) + (if-let ((params (assoc-default 'params atoms))) + (should (equal ours params)) + (should-not ours)))))) + +(defun erc-d-tests--new-ex (existing raw-hunk) + (let* ((f (lambda (_) (pop raw-hunk))) + (sd (make-erc-d-u-scan-d :f f))) + (setf (erc-d-exchange-hunk existing) (make-erc-d-u-scan-e :sd sd) + (erc-d-exchange-spec existing) (make-erc-d-spec))) + (erc-d--iter existing)) + +(ert-deftest erc-d--render-entries () + (let* ((erc-nick "foo") + (dialog (make-erc-d-dialog :vars `((:a . 1) + (c . ((a b) (: a space b))) + (d . (c alpha digit)) + (bee . 2) + (f . ,(lambda () "3")) + (i . erc-nick)))) + (exchange (make-erc-d-exchange :dialog dialog)) + (mex (apply-partially #'erc-d-tests--new-ex exchange)) + it) + + (erc-d-exchange-reload dialog exchange) + + (ert-info ("Baseline Outgoing") + (setq it (funcall mex '((0 "abc")))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "abc"))) + + (ert-info ("Incoming are regexp escaped") + (setq it (funcall mex '((i 0.0 "fsf" ".org")))) + (should (equal (cons (funcall it) (funcall it)) '(i . 0.0))) + (should (equal (funcall it) "\\`fsf\\.org"))) + + (ert-info ("Incoming can access vars via rx-let") + (setq it (funcall mex '((i 0.0 bee)))) + (should (equal (cons (funcall it) (funcall it)) '(i . 0.0))) + (should (equal (funcall it) "\\`\002"))) + + (ert-info ("Incoming rx-let params") + (setq it (funcall mex '((i 0.0 d)))) + (should (equal (cons (funcall it) (funcall it)) '(i . 0.0))) + (should (equal (funcall it) "\\`[[:alpha:]][[:space:]][[:digit:]]"))) + + (ert-info ("Incoming literal rx forms") + (setq it (funcall mex '((i 0.0 (= 3 alpha) ".org")))) + (should (equal (cons (funcall it) (funcall it)) '(i . 0.0))) + (should (equal (funcall it) "\\`[[:alpha:]]\\{3\\}\\.org"))) + + (ert-info ("Self-quoting disallowed") + (setq it (funcall mex '((0 :a "abc")))) + (should (equal (funcall it) 0)) + (should-error (funcall it))) + + (ert-info ("Global vars and short vars") + (setq it (funcall mex '((0 i f erc-nick)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "foo3foo"))) + + (ert-info ("Exits clean") + (when (listp (alist-get 'f (erc-d-dialog-vars dialog))) ; may be compiled + (should (eq 'closure (car (alist-get 'f (erc-d-dialog-vars dialog)))))) + (should-not (funcall it)) + (should (equal (erc-d-dialog-vars dialog) + `((:a . 1) + (c . ((a b) (: a space b))) + (d . (c alpha digit)) + (bee . 2) + (f . ,(alist-get 'f (erc-d-dialog-vars dialog))) + (i . erc-nick))))))) + +(ert-deftest erc-d--render-entries--matches () + (let* ((alist (list + (cons 'f (lambda (a) (funcall a :match 1))) + (cons 'g (lambda () (match-string 2 "foo bar baz"))) + (cons 'h (lambda (a) (concat (funcall a :match 0) + (funcall a :request)))) + (cons 'i (lambda (_ e) (erc-d-exchange-request e))) + (cons 'j (lambda () + (set-match-data '(0 1)) + (match-string 0 "j"))))) + (dialog (make-erc-d-dialog :vars alist)) + (exchange (make-erc-d-exchange :dialog dialog + :request "foo bar baz" + ;; 11 222 + :match-data '(4 11 4 6 8 11))) + (mex (apply-partially #'erc-d-tests--new-ex exchange)) + it) + + (erc-d-exchange-reload dialog exchange) + + (ert-info ("One arg, match") + (setq it (funcall mex '((0 f)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "ba"))) + + (ert-info ("No args") + (setq it (funcall mex '((0 g)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "baz"))) + + (ert-info ("Second arg is exchange object") + (setq it (funcall mex '((0 i)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "foo bar baz"))) + + (ert-info ("One arg, multiple calls") + (setq it (funcall mex '((0 h)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "bar bazfoo bar baz"))) + + (ert-info ("Match data restored") + (setq it (funcall mex '((0 j)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "j")) + + (setq it (funcall mex '((0 g)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "baz"))) + + (ert-info ("Bad signature") + (let ((qlist (list 'f '(lambda (p q x) (ignore))))) + (setf (erc-d-dialog-vars dialog) qlist) + (should-error (erc-d-exchange-reload dialog exchange)))))) + +(ert-deftest erc-d--render-entries--dynamic () + (let* ((alist (list + (cons 'foo "foo") + (cons 'f (lambda (a) (funcall a :get-binding 'foo))) + (cons 'h (lambda (a) (upcase (funcall a :get-var 'foo)))) + (cons 'g (lambda (a) + (funcall a :rebind 'g (funcall a :get-var 'f)) + "bar")) + (cons 'j (lambda (a) (funcall a :set "123") "abc")) + (cons 'k (lambda () "abc")))) + (dialog (make-erc-d-dialog :vars alist)) + (exchange (make-erc-d-exchange :dialog dialog)) + (mex (apply-partially #'erc-d-tests--new-ex exchange)) + it) + + (erc-d-exchange-reload dialog exchange) + + (ert-info ("Initial reference calls function") + (setq it (funcall mex '((0 j) (0 j)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "abc"))) + + (ert-info ("Subsequent reference expands to string") + (should (equal (funcall it) 0)) + (should (equal (funcall it) "123"))) + + (ert-info ("Outside manipulation: initial reference calls function") + (setq it (funcall mex '((0 k) (0 k)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "abc"))) + + (ert-info ("Outside manipulation: subsequent reference expands to string") + (erc-d-exchange-rebind dialog exchange 'k "123") + (should (equal (funcall it) 0)) + (should (equal (funcall it) "123"))) + + (ert-info ("Swap one function for another") + (setq it (funcall mex '((0 g) (0 g)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "bar")) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "foo"))) + + (ert-info ("Bindings accessible inside functions") + (setq it (funcall mex '((0 f h)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "fooFOO"))) + + (ert-info ("Rebuild alist by sending flag") + (setq it (funcall mex '((0 f) (1 f) (2 f) (i 3 f)))) + (should (equal (funcall it) 0)) + (should (equal (funcall it) "foo")) + (erc-d-exchange-rebind dialog exchange 'f "bar") + (should (equal (funcall it) 1)) + (should (equal (funcall it) "bar")) + (setq alist (setf (alist-get 'f (erc-d-dialog-vars dialog)) + (lambda nil "baz"))) + (should (eq (funcall it) 2)) + (should (equal (funcall it 'reload) "baz")) + (setq alist (setf (alist-get 'f (erc-d-dialog-vars dialog)) "spam")) + (should (eq (funcall it) 'i)) + (should (eq (funcall it 'reload) 3)) + (should (equal (funcall it) "\\`spam"))))) + +(ert-deftest erc-d-t-with-cleanup () + (should-not (get-buffer "*echo*")) + (should-not (get-buffer "*foo*")) + (should-not (get-buffer "*bar*")) + (should-not (get-buffer "*baz*")) + (erc-d-t-with-cleanup + ((echo (start-process "echo" (get-buffer-create "*echo*") "sleep" "1")) + (buffer-foo (get-buffer-create "*foo*")) + (buffer-bar (get-buffer-create "*bar*")) + (clean-up (list (intern (process-name echo)))) ; let* + buffer-baz) + (ert-info ("Clean Up") + (should (equal clean-up '(ran echo))) + (should (bufferp buffer-baz)) + (should (bufferp buffer-foo)) + (setq buffer-foo nil)) + (setq buffer-baz (get-buffer-create "*baz*")) + (push 'ran clean-up)) + (ert-info ("Buffers and procs destroyed") + (should-not (get-buffer "*echo*")) + (should-not (get-buffer "*bar*")) + (should-not (get-buffer "*baz*"))) + (ert-info ("Buffer foo spared") + (should (get-buffer "*foo*")) + (kill-buffer "*foo*"))) + +(ert-deftest erc-d-t-wait-for () + :tags '(:unstable) + (let (v) + (run-at-time 0.2 nil (lambda () (setq v t))) + (should (erc-d-t-wait-for 0.4 "result becomes non-nil" v)) + (should-error (erc-d-t-wait-for 0.4 "result stays nil" (not v))) + (setq v nil) + (should-not (erc-d-t-wait-for -0.4 "inverted stays nil" v)) + (run-at-time 0.2 nil (lambda () (setq v t))) + (setq v nil) + (should-error (erc-d-t-wait-for -0.4 "inverted becomes non-nil" v)))) + +(defvar erc-d-tests-with-server-password "changeme") + +;; Compromise between removing `autojoin' from `erc-modules' entirely +;; and allowing side effects to meddle excessively +(defvar erc-autojoin-channels-alist) + +;; This is only meant to be used by tests in this file. +(cl-defmacro erc-d-tests-with-server ((dumb-server-var erc-server-buffer-var) + dialog &rest body) + "Create server for DIALOG and run BODY. +DIALOG may also be a list of dialogs. ERC-SERVER-BUFFER-VAR and +DUMB-SERVER-VAR are bound accordingly in BODY." + (declare (indent 2)) + (when (eq '_ dumb-server-var) + (setq dumb-server-var (make-symbol "dumb-server-var"))) + (when (eq '_ erc-server-buffer-var) + (setq erc-server-buffer-var (make-symbol "erc-server-buffer-var"))) + (if (listp dialog) + (setq dialog (mapcar (lambda (f) (list 'quote f)) dialog)) + (setq dialog `((quote ,dialog)))) + `(let* (auth-source-do-cache + (,dumb-server-var (erc-d-run "localhost" t ,@dialog)) + ,erc-server-buffer-var + ;; + (erc-server-flood-penalty 0.05) + erc-autojoin-channels-alist + erc-server-auto-reconnect) + (should-not erc-d--slow-mo) + (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting")) + ;; Allow important messages through, even in -batch mode. + (advice-add #'erc-handle-login :around #'erc-d-t-silence-around) + (advice-add #'erc-server-connect :around #'erc-d-t-silence-around) + (unless (or noninteractive erc-debug-irc-protocol) + (erc-toggle-debug-irc-protocol)) + (setq ,erc-server-buffer-var + (erc :server "localhost" + :password erc-d-tests-with-server-password + :port (process-contact ,dumb-server-var :service) + :nick "tester" + :full-name "tester")) + (unwind-protect + (progn + ,@body + (erc-d-t-wait-for 1 "dumb-server death" + (not (process-live-p ,dumb-server-var)))) + (when (process-live-p erc-server-process) + (delete-process erc-server-process)) + (advice-remove #'erc-handle-login #'erc-d-t-silence-around) + (advice-remove #'erc-server-connect #'erc-d-t-silence-around) + (when noninteractive + (kill-buffer ,erc-server-buffer-var) + (erc-d-t-kill-related-buffers))))) + +(defmacro erc-d-tests-with-failure-spy (found func-syms &rest body) + "Wrap functions with advice for inspecting errors caused by BODY. +Do this for functions whose names appear in FUNC-SYMS. When running +advice code, add errors to list FOUND. Note: the teardown finalizer is +not added by default. Also, `erc-d-linger-secs' likely has to be +nonzero for this to work." + (declare (indent 2)) + ;; Catch errors thrown by timers that `should-error'ignores + `(progn + (let ((ad (lambda (f o &rest r) + (condition-case err + (apply o r) + (error (push err ,found) + (advice-remove f 'spy)))))) + (dolist (sym ,func-syms) + (advice-add sym :around (apply-partially ad sym) '((name . spy))))) + (progn ,@body) + (dolist (sym ,func-syms) + (advice-remove sym 'spy)) + (setq ,found (nreverse ,found)))) + +(ert-deftest erc-d-run-nonstandard-messages () + :tags '(:expensive-test) + (let* ((erc-d-linger-secs 0.2) + (dumb-server (erc-d-run "localhost" t 'nonstandard)) + (dumb-server-buffer (get-buffer "*erc-d-server*")) + (expect (erc-d-t-make-expecter)) + client) + (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting")) + (setq client (open-network-stream "erc-d-client" nil + "localhost" + (process-contact dumb-server :service) + :coding 'binary)) + (ert-info ("Server splits CRLF delimited lines") + (process-send-string client "ONE one\r\nTWO two\r\n") + (with-current-buffer dumb-server-buffer + (funcall expect 1 '(: "<- nonstandard:" (+ digit) " ONE one" eol)) + (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ TWO two$")))) + (ert-info ("Server doesn't discard empty lines") + (process-send-string client "\r\n") + (with-current-buffer dumb-server-buffer + (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ $")))) + (ert-info ("Server preserves spaces") + (process-send-string client " \r\n") + (with-current-buffer dumb-server-buffer + (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ \\{2\\}$"))) + (process-send-string client " \r\n") + (with-current-buffer dumb-server-buffer + (funcall expect 1 '(regex "<- nonstandard:[[:digit:]]+ \\{3\\}$")))) + (erc-d-t-wait-for 3 "dumb-server death" + (not (process-live-p dumb-server))) + (delete-process client) + (when noninteractive + (kill-buffer dumb-server-buffer)))) + +(ert-deftest erc-d-run-basic () + :tags '(:expensive-test) + (erc-d-tests-with-server (_ _) basic + (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan")) + (erc-d-t-search-for 2 "hey")) + (when noninteractive + (kill-buffer "#chan")))) + +(ert-deftest erc-d-run-eof () + :tags '(:expensive-test) + (skip-unless noninteractive) + (erc-d-tests-with-server (_ erc-s-buf) eof + (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan")) + (erc-d-t-search-for 2 "hey")) + (with-current-buffer erc-s-buf + (process-send-eof erc-server-process)))) + +(ert-deftest erc-d-run-eof-fail () + :tags '(:expensive-test) + (let (errors) + (erc-d-tests-with-failure-spy errors '(erc-d--teardown) + (erc-d-tests-with-server (_ _) eof + (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan")) + (erc-d-t-search-for 2 "hey")) + (erc-d-t-wait-for 10 errors))) + (should (string-match-p "Timed out awaiting request.*__EOF__" + (cadr (pop errors)))))) + +(ert-deftest erc-d-run-linger () + :tags '(:expensive-test) + (erc-d-tests-with-server (dumb-s _) linger + (with-current-buffer (erc-d-t-wait-for 6 (get-buffer "#chan")) + (erc-d-t-search-for 2 "hey")) + (with-current-buffer (process-buffer dumb-s) + (erc-d-t-search-for 2 "Lingering for 1.00 seconds")) + (with-current-buffer (process-buffer dumb-s) + (erc-d-t-search-for 3 "Lingered for 1.00 seconds")))) + +(ert-deftest erc-d-run-linger-fail () + :tags '(:expensive-test) + (let ((erc-server-flood-penalty 0.1) + errors) + (erc-d-tests-with-failure-spy + errors '(erc-d--teardown erc-d-command) + (erc-d-tests-with-server (_ _) linger + (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan")) + (erc-d-t-search-for 2 "hey") + (erc-cmd-MSG "#chan hi")) + (erc-d-t-wait-for 10 "Bad match" errors))) + (should (string-match-p "Match failed.*hi" (cadr (pop errors)))))) + +(ert-deftest erc-d-run-linger-direct () + :tags '(:expensive-test) + (let* ((dumb-server (erc-d-run "localhost" t + 'linger-multi-a 'linger-multi-b)) + (port (process-contact dumb-server :service)) + (dumb-server-buffer (get-buffer "*erc-d-server*")) + (client-buffer-a (get-buffer-create "*erc-d-client-a*")) + (client-buffer-b (get-buffer-create "*erc-d-client-b*")) + (start (current-time)) + client-a client-b) + (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting")) + (setq client-a (open-network-stream "erc-d-client-a" client-buffer-a + "localhost" port + :coding 'binary) + client-b (open-network-stream "erc-d-client-b" client-buffer-b + "localhost" port + :coding 'binary)) + (process-send-string client-a "PASS :a\r\n") + (sleep-for 0.01) + (process-send-string client-b "PASS :b\r\n") + (sleep-for 0.01) + (erc-d-t-wait-for 3 "dumb-server death" + (not (process-live-p dumb-server))) + (ert-info ("Ensure linger of one second") + (should (time-less-p 1 (time-subtract (current-time) start))) + (should (time-less-p (time-subtract (current-time) start) 1.5))) + (delete-process client-a) + (delete-process client-b) + (when noninteractive + (kill-buffer client-buffer-a) + (kill-buffer client-buffer-b) + (kill-buffer dumb-server-buffer)))) + +(ert-deftest erc-d-run-drop-direct () + :tags '(:unstable) + (let* ((dumb-server (erc-d-run "localhost" t 'drop-a 'drop-b)) + (port (process-contact dumb-server :service)) + (dumb-server-buffer (get-buffer "*erc-d-server*")) + (client-buffer-a (get-buffer-create "*erc-d-client-a*")) + (client-buffer-b (get-buffer-create "*erc-d-client-b*")) + (start (current-time)) + client-a client-b) + (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting")) + (setq client-a (open-network-stream "erc-d-client-a" client-buffer-a + "localhost" port + :coding 'binary) + client-b (open-network-stream "erc-d-client-b" client-buffer-b + "localhost" port + :coding 'binary)) + (process-send-string client-a "PASS :a\r\n") + (sleep-for 0.01) + (process-send-string client-b "PASS :b\r\n") + (erc-d-t-wait-for 3 "client-a dies" (not (process-live-p client-a))) + (should (time-less-p (time-subtract (current-time) start) 0.32)) + (erc-d-t-wait-for 3 "dumb-server death" + (not (process-live-p dumb-server))) + (ert-info ("Ensure linger of one second") + (should (time-less-p 1 (time-subtract (current-time) start)))) + (delete-process client-a) + (delete-process client-b) + (when noninteractive + (kill-buffer client-buffer-a) + (kill-buffer client-buffer-b) + (kill-buffer dumb-server-buffer)))) + +(ert-deftest erc-d-run-no-match () + :tags '(:expensive-test) + (let ((erc-d-linger-secs 1) + erc-server-auto-reconnect + errors) + (erc-d-tests-with-failure-spy errors '(erc-d--teardown erc-d-command) + (erc-d-tests-with-server (_ erc-server-buffer) no-match + (with-current-buffer erc-server-buffer + (erc-d-t-search-for 2 "away") + (erc-cmd-JOIN "#foo") + (erc-d-t-wait-for 10 "Bad match" errors)))) + (should (string-match-p "Match failed.*foo.*chan" (cadr (pop errors)))) + (should-not (get-buffer "#foo")))) + +(ert-deftest erc-d-run-timeout () + :tags '(:expensive-test) + (let ((erc-d-linger-secs 1) + err errors) + (erc-d-tests-with-failure-spy errors '(erc-d--teardown) + (erc-d-tests-with-server (_ _) timeout + (erc-d-t-wait-for 10 "error caught" errors))) + (setq err (pop errors)) + (should (eq (car err) 'erc-d-timeout)) + (should (string-match-p "Timed out" (cadr err))))) + +(ert-deftest erc-d-run-unexpected () + :tags '(:expensive-test) + (let ((erc-d-linger-secs 2) + errors) + (erc-d-tests-with-failure-spy + errors '(erc-d--teardown erc-d-command) + (erc-d-tests-with-server (_ _) unexpected + (ert-info ("All specs consumed when more input arrives") + (erc-d-t-wait-for 10 "error caught" (cdr errors))))) + (should (string-match-p "unexpected.*MODE" (cadr (pop errors)))) + ;; Nonsensical normally because func would have already exited when + ;; first error was thrown + (should (string-match-p "Match failed" (cadr (pop errors)))))) + +(ert-deftest erc-d-run-unexpected-depleted () + :tags '(:expensive-test) + (let ((erc-d-linger-secs 3) + errors) + (erc-d-tests-with-failure-spy errors '(erc-d--teardown erc-d-command) + (let* ((dumb-server-buffer (get-buffer-create "*erc-d-server*")) + (dumb-server (erc-d-run "localhost" t 'depleted)) + (expect (erc-d-t-make-expecter)) + (client-buf (get-buffer-create "*erc-d-client*")) + client-proc) + (with-current-buffer dumb-server-buffer + (erc-d-t-search-for 3 "Starting")) + (setq client-proc (make-network-process + :buffer client-buf + :name "erc-d-client" + :family 'ipv4 + :noquery t + :coding 'binary + :service (process-contact dumb-server :service) + :host "localhost")) + (with-current-buffer dumb-server-buffer + (funcall expect 3 "open from")) + (process-send-string client-proc "PASS :changeme\r\n") + (sleep-for 0.01) + (process-send-string client-proc "NICK tester\r\n") + (sleep-for 0.01) + (process-send-string client-proc "USER user 0 * :tester\r\n") + (sleep-for 0.01) + (when (process-live-p client-proc) + (process-send-string client-proc "BLAH :too much\r\n") + (sleep-for 0.01)) + (with-current-buffer client-buf + (funcall expect 3 "Welcome to the Internet")) + (erc-d-t-wait-for 2 "dumb-server death" + (not (process-live-p dumb-server))) + (delete-process client-proc) + (when noninteractive + (kill-buffer client-buf) + (kill-buffer dumb-server-buffer)))) + (should (string-match-p "unexpected.*BLAH" (cadr (pop errors)))) + ;; Wouldn't happen IRL + (should (string-match-p "unexpected.*BLAH" (cadr (pop errors)))) + (should-not errors))) + +(defun erc-d-tests--dynamic-match-user (_dialog exchange) + "Shared pattern/response handler for canned dynamic DIALOG test." + (should (string= (match-string 1 (erc-d-exchange-request exchange)) + "tester"))) + +(defun erc-d-tests--run-dynamic () + "Perform common assertions for \"dynamic\" dialog." + (erc-d-tests-with-server (dumb-server erc-server-buffer) dynamic + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (erc-d-t-search-for 2 "tester: hey")) + (with-current-buffer erc-server-buffer + (let ((expect (erc-d-t-make-expecter))) + (funcall expect 2 "host is irc.fsf.org") + (funcall expect 2 "modes for tester"))) + (with-current-buffer (process-buffer dumb-server) + (erc-d-t-search-for 2 "irc.fsf.org")) + (when noninteractive + (kill-buffer "#chan")))) + +(ert-deftest erc-d-run-dynamic-default-match () + :tags '(:expensive-test) + (let* (dynamic-tally + (erc-d-tmpl-vars '((user . "user") + (ignored . ((a b) (: a space b))) + (realname . (group (+ graph))))) + (nick (lambda (a) + (push '(nick . match-user) dynamic-tally) + (funcall a :set (funcall a :match 1) 'export))) + (dom (lambda (a) + (push '(dom . match-user) dynamic-tally) + (funcall a :set erc-d-server-fqdn))) + (erc-d-match-handlers + (list :user (lambda (d e) + (erc-d-exchange-rebind d e 'nick nick) + (erc-d-exchange-rebind d e 'dom dom) + (erc-d-tests--dynamic-match-user d e)) + :mode-user (lambda (d e) + (erc-d-exchange-rebind d e 'nick "tester") + (erc-d-exchange-rebind d e 'dom dom)))) + (erc-d-server-fqdn "irc.fsf.org")) + (erc-d-tests--run-dynamic) + (should (equal '((dom . match-user) (nick . match-user) (dom . match-user)) + dynamic-tally)))) + +(ert-deftest erc-d-run-dynamic-default-match-rebind () + :tags '(:expensive-test) + (let* (tally + ;; + (erc-d-tmpl-vars '((user . "user") + (ignored . ((a b) (: a space b))) + (realname . (group (+ graph))))) + (erc-d-match-handlers + (list :user + (lambda (d e) + (erc-d-exchange-rebind + d e 'nick + (lambda (a) + (push 'bind-nick tally) + (funcall a :rebind 'nick (funcall a :match 1) 'export))) + (erc-d-exchange-rebind + d e 'dom + (lambda () + (push 'bind-dom tally) + (erc-d-exchange-rebind d e 'dom erc-d-server-fqdn))) + (erc-d-tests--dynamic-match-user d e)) + :mode-user + (lambda (d e) + (erc-d-exchange-rebind d e 'nick "tester") + (erc-d-exchange-rebind d e 'dom erc-d-server-fqdn)))) + (erc-d-server-fqdn "irc.fsf.org")) + (erc-d-tests--run-dynamic) + (should (equal '(bind-nick bind-dom) tally)))) + +(ert-deftest erc-d-run-dynamic-runtime-stub () + :tags '(:expensive-test) + (let ((erc-d-tmpl-vars '((token . (group (or "barnet" "foonet"))))) + (erc-d-match-handlers + (list :pass (lambda (d _e) + (erc-d-load-replacement-dialog d 'dynamic-foonet)))) + (erc-d-tests-with-server-password "foonet:changeme")) + (erc-d-tests-with-server (_ erc-server-buffer) + (dynamic-stub dynamic-foonet) + (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan")) + (erc-d-t-search-for 2 "alice:") + (erc-d-t-absent-for 0.1 "joe")) + (with-current-buffer erc-server-buffer + (let ((expect (erc-d-t-make-expecter))) + (funcall expect 2 "host is irc.foonet.org") + (funcall expect 2 "NETWORK=FooNet"))) + (when noninteractive + (kill-buffer "#chan"))))) + +(ert-deftest erc-d-run-dynamic-runtime-stub-skip () + :tags '(:expensive-test) + (let ((erc-d-tmpl-vars '((token . "barnet"))) + (erc-d-match-handlers + (list :pass (lambda (d _e) + (erc-d-load-replacement-dialog + d 'dynamic-barnet 1)))) + (erc-d-tests-with-server-password "barnet:changeme")) + (erc-d-tests-with-server (_ erc-server-buffer) + (dynamic-stub dynamic-barnet) + (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan")) + (erc-d-t-search-for 2 "joe:") + (erc-d-t-absent-for 0.1 "alice")) + (with-current-buffer erc-server-buffer + (let ((expect (erc-d-t-make-expecter))) + (funcall expect 2 "host is irc.barnet.org") + (funcall expect 2 "NETWORK=BarNet"))) + (when noninteractive + (kill-buffer "#chan"))))) + +;; Two servers, in-process, one client per +(ert-deftest erc-d-run-dual-direct () + :tags '(:expensive-test) + (let* ((erc-d--slow-mo -1) + (server-a (erc-d-run "localhost" t "erc-d-server-a" 'dynamic-foonet)) + (server-b (erc-d-run "localhost" t "erc-d-server-b" 'dynamic-barnet)) + (server-a-buffer (get-buffer "*erc-d-server-a*")) + (server-b-buffer (get-buffer "*erc-d-server-b*")) + (client-a-buffer (get-buffer-create "*erc-d-client-a*")) + (client-b-buffer (get-buffer-create "*erc-d-client-b*")) + client-a client-b) + (with-current-buffer server-a-buffer (erc-d-t-search-for 4 "Starting")) + (with-current-buffer server-b-buffer (erc-d-t-search-for 4 "Starting")) + (setq client-a (make-network-process + :buffer client-a-buffer + :name "erc-d-client-a" + :family 'ipv4 + :noquery t + :coding 'binary + :service (process-contact server-a :service) + :host "localhost") + client-b (make-network-process + :buffer client-b-buffer + :name "erc-d-client-b" + :family 'ipv4 + :noquery t + :coding 'binary + :service (process-contact server-b :service) + :host "localhost")) + ;; Also tests slo-mo indirectly because FAKE would fail without it + (process-send-string client-a "NICK tester\r\n") + (process-send-string client-b "FAKE noop\r\nNICK tester\r\n") + (sleep-for 0.01) + (process-send-string client-a "USER user 0 * :tester\r\n") + (process-send-string client-b "USER user 0 * :tester\r\n") + (sleep-for 0.01) + (process-send-string client-a "MODE tester +i\r\n") + (process-send-string client-b "MODE tester +i\r\n") + (sleep-for 0.01) + (process-send-string client-a "MODE #chan\r\n") + (process-send-string client-b "MODE #chan\r\n") + (sleep-for 0.01) + (erc-d-t-wait-for 2 "server-a death" (not (process-live-p server-a))) + (erc-d-t-wait-for 2 "server-b death" (not (process-live-p server-b))) + (when noninteractive + (kill-buffer client-a-buffer) + (kill-buffer client-b-buffer) + (kill-buffer server-a-buffer) + (kill-buffer server-b-buffer)))) + +;; This can be removed; only exists to get a baseline for next test +(ert-deftest erc-d-run-fuzzy-direct () + :tags '(:expensive-test) + (let* ((erc-d-tmpl-vars + `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t))))) + (dumb-server (erc-d-run "localhost" t 'fuzzy)) + (dumb-server-buffer (get-buffer "*erc-d-server*")) + (client-buffer (get-buffer-create "*erc-d-client*")) + client) + (with-current-buffer "*erc-d-server*" (erc-d-t-search-for 4 "Starting")) + (setq client (make-network-process + :buffer client-buffer + :name "erc-d-client" + :family 'ipv4 + :noquery t + :coding 'binary + :service (process-contact dumb-server :service) + :host "localhost")) + ;; We could also just send this as a single fatty + (process-send-string client "PASS :changeme\r\n") + (sleep-for 0.01) + (process-send-string client "NICK tester\r\n") + (sleep-for 0.01) + (process-send-string client "USER user 0 * :tester\r\n") + (sleep-for 0.01) + (process-send-string client "MODE tester +i\r\n") + (sleep-for 0.01) + (process-send-string client "JOIN #bar\r\n") + (sleep-for 0.01) + (process-send-string client "JOIN #foo\r\n") + (sleep-for 0.01) + (process-send-string client "MODE #bar\r\n") + (sleep-for 0.01) + (process-send-string client "MODE #foo\r\n") + (sleep-for 0.01) + (erc-d-t-wait-for 1 "dumb-server death" + (not (process-live-p dumb-server))) + (when noninteractive + (kill-buffer client-buffer) + (kill-buffer dumb-server-buffer)))) + +;; Without adjusting penalty, takes ~15 secs. With is comprable to direct ^. +(ert-deftest erc-d-run-fuzzy () + :tags '(:expensive-test) + (let ((erc-server-flood-penalty 1.2) ; penalty < margin/sends is basically 0 + (erc-d-linger-secs 0.1) + (erc-d-tmpl-vars + `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t))))) + erc-server-auto-reconnect) + (erc-d-tests-with-server (_ erc-server-buffer) fuzzy + (with-current-buffer erc-server-buffer + (erc-d-t-search-for 2 "away") + (goto-char erc-input-marker) + (erc-cmd-JOIN "#bar")) + (erc-d-t-wait-for 2 (get-buffer "#bar")) + (with-current-buffer erc-server-buffer + (erc-cmd-JOIN "#foo")) + (erc-d-t-wait-for 20 (get-buffer "#foo")) + (with-current-buffer "#bar" + (erc-d-t-search-for 1 "was created on")) + (with-current-buffer "#foo" + (erc-d-t-search-for 5 "was created on"))))) + +(ert-deftest erc-d-run-no-block () + :tags '(:expensive-test) + (let ((erc-server-flood-penalty 1) + (erc-d-linger-secs 1.2) + (expect (erc-d-t-make-expecter)) + erc-server-auto-reconnect) + (erc-d-tests-with-server (_ erc-server-buffer) no-block + (with-current-buffer erc-server-buffer + (funcall expect 2 "away") + (funcall expect 1 erc-prompt) + (with-current-buffer erc-server-buffer (erc-cmd-JOIN "#foo"))) + (with-current-buffer (erc-d-t-wait-for 2 (get-buffer "#foo")) + (funcall expect 2 "was created on")) + + (ert-info ("Join #bar") + (with-current-buffer erc-server-buffer (erc-cmd-JOIN "#bar")) + (erc-d-t-wait-for 2 (get-buffer "#bar"))) + + (with-current-buffer "#bar" (funcall expect 1 "was created on")) + + (ert-info ("Server expects next pattern but keeps sending") + (with-current-buffer "#foo" (funcall expect 2 "Rosalind, I will ")) + (with-current-buffer "#bar" (funcall expect 1 "hi 123")) + (with-current-buffer "#foo" + (should-not (search-forward " I am heard" nil t)) + (funcall expect 1.5 " I am heard")))))) + +(defun erc-d-tests--run-proxy-direct (dumb-server dumb-server-buffer port) + "Start DUMB-SERVER with DUMB-SERVER-BUFFER and PORT. +These are steps shared by in-proc and subproc variants testing a +bouncer-like setup." + (when (version< emacs-version "28") (ert-skip "TODO connection refused")) + (let ((client-buffer-foo (get-buffer-create "*erc-d-client-foo*")) + (client-buffer-bar (get-buffer-create "*erc-d-client-bar*")) + (expect (erc-d-t-make-expecter)) + client-foo + client-bar) + (setq client-foo (make-network-process + :buffer client-buffer-foo + :name "erc-d-client-foo" + :family 'ipv4 + :noquery t + :coding 'binary + :service port + :host "localhost") + client-bar (make-network-process + :buffer client-buffer-bar + :name "erc-d-client-bar" + :family 'ipv4 + :noquery t + :coding 'binary + :service port + :host "localhost")) + (with-current-buffer dumb-server-buffer + (funcall expect 3 "open from")) + (process-send-string client-foo "PASS :foo:changeme\r\n") + (process-send-string client-bar "PASS :bar:changeme\r\n") + (sleep-for 0.01) + (process-send-string client-foo "NICK tester\r\n") + (process-send-string client-bar "NICK tester\r\n") + (sleep-for 0.01) + (process-send-string client-foo "USER user 0 * :tester\r\n") + (process-send-string client-bar "USER user 0 * :tester\r\n") + (sleep-for 0.01) + (process-send-string client-foo "MODE tester +i\r\n") + (process-send-string client-bar "MODE tester +i\r\n") + (sleep-for 0.01) + (with-current-buffer client-buffer-foo + (funcall expect 3 "FooNet") + (funcall expect 3 "irc.foo.net") + (funcall expect 3 "marked as being away") + (goto-char (point-min)) + (should-not (search-forward "bar" nil t))) + (with-current-buffer client-buffer-bar + (funcall expect 3 "BarNet") + (funcall expect 3 "irc.bar.net") + (funcall expect 3 "marked as being away") + (goto-char (point-min)) + (should-not (search-forward "foo" nil t))) + (erc-d-t-wait-for 2 "dumb-server death" + (not (process-live-p dumb-server))) + (delete-process client-foo) + (delete-process client-bar) + (when noninteractive + (kill-buffer client-buffer-foo) + (kill-buffer client-buffer-bar) + (kill-buffer dumb-server-buffer)))) + +;; This test shows the simplest way to set up template variables: put +;; everything needed for the whole session in `erc-d-tmpl-vars' before +;; starting the server. + +(ert-deftest erc-d-run-proxy-direct-spec-vars () + :tags '(:expensive-test) + (let* ((dumb-server-buffer (get-buffer-create "*erc-d-server*")) + (erc-d-linger-secs 0.5) + (erc-d-tmpl-vars + `((network . (group (+ alpha))) + (fqdn . ,(lambda (a) + (let ((network (funcall a :match 1 'pass))) + (should (member network '("foo" "bar"))) + (funcall a :set (concat "irc." network ".net"))))) + (net . ,(lambda (a) + (let ((network (funcall a :match 1 'pass))) + (should (member network '("foo" "bar"))) + (concat (capitalize network) "Net")))))) + (dumb-server (erc-d-run "localhost" t 'proxy-foonet 'proxy-barnet)) + (port (process-contact dumb-server :service))) + (with-current-buffer dumb-server-buffer + (erc-d-t-search-for 3 "Starting")) + (erc-d-tests--run-proxy-direct dumb-server dumb-server-buffer port))) + +(cl-defun erc-d-tests--start-server (&key dialogs buffer linger program libs) + "Start and return a server in a subprocess using BUFFER and PORT. +DIALOGS are symbols representing the base names of dialog files in +`erc-d-u-canned-dialog-dir'. LIBS are extra files to load." + (push (locate-library "erc-d" nil (list erc-d-u--library-directory)) libs) + (cl-assert (car libs)) + (let* ((args `("erc-d-server" ,buffer + ,(concat invocation-directory invocation-name) + "-Q" "-batch" "-L" ,erc-d-u--library-directory + ,@(let (o) (while libs (push (pop libs) o) (push "-l" o)) o) + "-eval" ,(format "%S" program) "-f" "erc-d-serve" + ,@(when linger (list "--linger" (number-to-string linger))) + ,@(mapcar #'erc-d-u--expand-dialog-symbol dialogs))) + (proc (apply #'start-process args))) + (set-process-query-on-exit-flag proc nil) + (with-current-buffer buffer + (erc-d-t-search-for 5 "Starting") + (search-forward " (") + (backward-char)) + (let ((pair (read buffer))) + (cons proc (cdr pair))))) + +(ert-deftest erc-d-run-proxy-direct-subprocess () + :tags '(:expensive-test) + (let* ((buffer (get-buffer-create "*erc-d-server*")) + ;; These are quoted because they're passed as printed forms to subproc + (fqdn '(lambda (a e) + (let* ((d (erc-d-exchange-dialog e)) + (name (erc-d-dialog-name d))) + (funcall a :set (if (eq name 'proxy-foonet) + "irc.foo.net" + "irc.bar.net"))))) + (net '(lambda (a) + (funcall a :rebind 'net + (if (eq (funcall a :dialog-name) 'proxy-foonet) + "FooNet" + "BarNet")))) + (program `(setq erc-d-tmpl-vars '((fqdn . ,fqdn) + (net . ,net) + (network . (group (+ alpha)))))) + (port (erc-d-tests--start-server + :linger 0.3 + :program program + :buffer buffer + :dialogs '(proxy-foonet proxy-barnet))) + (server (pop port))) + (erc-d-tests--run-proxy-direct server buffer port))) + +(ert-deftest erc-d-run-proxy-direct-subprocess-lib () + :tags '(:expensive-test) + (let* ((buffer (get-buffer-create "*erc-d-server*")) + (lib (expand-file-name "proxy-subprocess.el" + (ert-resource-directory))) + (port (erc-d-tests--start-server :linger 0.3 + :buffer buffer + :dialogs '(proxy-foonet proxy-barnet) + :libs (list lib))) + (server (pop port))) + (erc-d-tests--run-proxy-direct server buffer port))) + +(ert-deftest erc-d-run-no-pong () + :tags '(:expensive-test) + (let* (erc-d-auto-pong + ;; + (erc-d-tmpl-vars + `((nonce . (group (: digit digit))) + (echo . ,(lambda (a) + (should (string= (funcall a :match 1) "42")) "42")))) + (dumb-server-buffer (get-buffer-create "*erc-d-server*")) + (dumb-server (erc-d-run "localhost" t 'no-pong)) + (expect (erc-d-t-make-expecter)) + (client-buf (get-buffer-create "*erc-d-client*")) + client-proc) + (with-current-buffer dumb-server-buffer + (erc-d-t-search-for 3 "Starting")) + (setq client-proc (make-network-process + :buffer client-buf + :name "erc-d-client" + :family 'ipv4 + :noquery t + :coding 'binary + :service (process-contact dumb-server :service) + :host "localhost")) + (with-current-buffer dumb-server-buffer + (funcall expect 3 "open from")) + (process-send-string client-proc "PASS :changeme\r\nNICK tester\r\n") + (sleep-for 0.01) + (process-send-string client-proc "USER user 0 * :tester\r\n") + (sleep-for 0.01) + (process-send-string client-proc "MODE tester +i\r\n") + (sleep-for 0.01) + (with-current-buffer client-buf + (funcall expect 3 "ExampleOrg") + (funcall expect 3 "irc.example.org") + (funcall expect 3 "marked as being away")) + (ert-info ("PING is not intercepted by specialized method") + (process-send-string client-proc "PING 42\r\n") + (with-current-buffer client-buf + (funcall expect 3 "PONG"))) + (erc-d-t-wait-for 2 "dumb-server death" + (not (process-live-p dumb-server))) + (delete-process client-proc) + (when noninteractive + (kill-buffer client-buf) + (kill-buffer dumb-server-buffer)))) + +;; Inspect replies as they arrive within a single exchange, i.e., ensure we +;; don't regress to prior buggy version in which inspection wasn't possible +;; until all replies had been sent by the server. +(ert-deftest erc-d-run-incremental () + :tags '(:expensive-test) + (let ((erc-server-flood-penalty 0) + (expect (erc-d-t-make-expecter)) + erc-d-linger-secs) + (erc-d-tests-with-server (_ erc-server-buffer) incremental + (with-current-buffer erc-server-buffer + (funcall expect 3 "marked as being away")) + (with-current-buffer erc-server-buffer + (erc-cmd-JOIN "#foo")) + (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo")) + (funcall expect 1 "Users on #foo") + (funcall expect 1 "Look for me") + (not (search-forward "Done" nil t)) + (funcall expect 10 "Done") + (erc-send-message "Hi"))))) + +(ert-deftest erc-d-unix-socket-direct () + :tags '(:expensive-test) + (skip-unless (featurep 'make-network-process '(:family local))) + (let* ((erc-d-linger-secs 0.1) + (sock (expand-file-name "erc-d.sock" temporary-file-directory)) + (dumb-server (erc-d-run nil sock 'basic)) + (dumb-server-buffer (get-buffer "*erc-d-server*")) + (client-buffer (get-buffer-create "*erc-d-client*")) + client) + (with-current-buffer "*erc-d-server*" + (erc-d-t-search-for 4 "Starting")) + (unwind-protect + (progn + (setq client (make-network-process + :buffer client-buffer + :name "erc-d-client" + :family 'local + :noquery t + :coding 'binary + :service sock)) + (process-send-string client "PASS :changeme\r\n") + (sleep-for 0.01) + (process-send-string client "NICK tester\r\n") + (sleep-for 0.01) + (process-send-string client "USER user 0 * :tester\r\n") + (sleep-for 0.1) + (process-send-string client "MODE tester +i\r\n") + (sleep-for 0.01) + (process-send-string client "MODE #chan\r\n") + (sleep-for 0.01) + (erc-d-t-wait-for 1 "dumb-server death" + (not (process-live-p dumb-server))) + (when noninteractive + (kill-buffer client-buffer) + (kill-buffer dumb-server-buffer))) + (delete-file sock)))) + +;;; 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 @@ +;;; erc-d-u.el --- Helpers for ERC test server -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see +;; . + +;;; Commentary: + +;; The utilities here are kept separate from those in `erc-d' so that +;; tests running the server in a subprocess can use them without +;; having to require the main lib. If migrating outside of test/lisp, +;; there may be no reason to continue this. +;; +;; Another (perhaps misguided) goal here is to avoid having ERC itself +;; as a dependency. +;; +;; FIXME this ^ is no longer the case (ERC is not a dependency) + +;;; Code: +(require 'rx) +(require 'subr-x) +(eval-when-compile (require 'ert)) + +(defvar erc-d-u--canned-buffers nil + "List of canned dialog buffers currently open for reading.") + +(cl-defstruct (erc-d-u-scan-d) ; dialog scanner + (buf nil :type buffer) + (done nil :type boolean) + (last nil :type integer) + (hunks nil :type (list-of marker)) + (f #'erc-d-u--read-exchange-default :type function)) + +(cl-defstruct (erc-d-u-scan-e) ; exchange scanner + (sd nil :type erc-d-u-scan-d) + (pos nil :type marker)) + +(defun erc-d-u--read-dialog (info) + "Read dialog file and stash relevant state in `erc-d-u-scan-d' INFO." + (if (and (buffer-live-p (erc-d-u-scan-d-buf info)) + (with-current-buffer (erc-d-u-scan-d-buf info) + (condition-case _err + (progn + (when (erc-d-u-scan-d-last info) + (goto-char (erc-d-u-scan-d-last info)) + (forward-list)) + (setf (erc-d-u-scan-d-last info) (point)) + (down-list) + (push (set-marker (make-marker) (point)) + (erc-d-u-scan-d-hunks info))) + ((end-of-buffer scan-error) + (setf (erc-d-u-scan-d-done info) t) + nil)))) + (make-erc-d-u-scan-e :sd info :pos (car (erc-d-u-scan-d-hunks info))) + (unless (erc-d-u-scan-d-hunks info) + (kill-buffer (erc-d-u-scan-d-buf info)) + nil))) + +(defun erc-d-u--read-exchange-default (info) + "Read from marker in exchange `erc-d-u-scan-e' object INFO." + (let ((hunks (erc-d-u-scan-e-sd info)) + (pos (erc-d-u-scan-e-pos info))) + (or (and (erc-d-u-scan-d-hunks hunks) + (with-current-buffer (erc-d-u-scan-d-buf hunks) + (goto-char pos) + (condition-case _err + (read pos) + ;; Raised unless malformed + (invalid-read-syntax + nil)))) + (unless (or (cl-callf (lambda (s) (delq pos s)) ; flip + (erc-d-u-scan-d-hunks hunks)) + (not (erc-d-u-scan-d-done hunks))) + (kill-buffer (erc-d-u-scan-d-buf hunks)) + nil)))) + +(defun erc-d-u--read-exchange (info) + "Call exchange reader assigned in `erc-d-u-scan-e' object INFO." + (funcall (erc-d-u-scan-d-f (erc-d-u-scan-e-sd info)) info)) + +(defun erc-d-u--canned-read (file) + "Dispense a reader for each exchange in dialog FILE." + (let ((buf (generate-new-buffer (file-name-nondirectory file)))) + (push buf erc-d-u--canned-buffers) + (with-current-buffer buf + (setq-local parse-sexp-ignore-comments t + coding-system-for-read 'utf-8) + (add-hook 'kill-buffer-hook + (lambda () (setq erc-d-u--canned-buffers + (delq buf erc-d-u--canned-buffers))) + nil 'local) + (insert-file-contents-literally file) + (lisp-data-mode)) + (make-erc-d-u-scan-d :buf buf))) + +(defvar erc-d-u--library-directory (file-name-directory load-file-name)) +(defvar erc-d-u-canned-dialog-dir + (file-name-as-directory (expand-file-name "resources" + erc-d-u--library-directory))) + +(defun erc-d-u--normalize-canned-name (dialog) + "Return DIALOG name as a symbol without validating it." + (if (symbolp dialog) + dialog + (intern (file-name-base dialog)))) + +(defvar erc-d-u-canned-file-name-extension ".eld") + +(defun erc-d-u--expand-dialog-symbol (dialog) + "Return filename based on symbol DIALOG." + (let ((name (symbol-name dialog))) + (unless (equal (file-name-extension name) + erc-d-u-canned-file-name-extension) + (setq name (concat name erc-d-u-canned-file-name-extension))) + (expand-file-name name erc-d-u-canned-dialog-dir))) + +(defun erc-d-u--massage-canned-name (dialog) + "Return DIALOG in a form acceptable to `erc-d-run'." + (if (or (symbolp dialog) (file-exists-p dialog)) + dialog + (erc-d-u--expand-dialog-symbol (intern dialog)))) + +(defun erc-d-u--canned-load-dialog (dialog) + "Load dispensing exchanges from DIALOG. +If DIALOG is a string, consider it a filename. Otherwise find a file +in `erc-d-u-canned-dialog-dir' with a base name matching the symbol's +name. + +Return an iterator that yields exchanges, each one an iterator of spec +forms. The first is a so-called request spec and the rest are composed +of zero or more response specs." + (when (symbolp dialog) + (setq dialog (erc-d-u--expand-dialog-symbol dialog))) + (unless (file-exists-p dialog) + (error "File not found: %s" dialog)) + (erc-d-u--canned-read dialog)) + +(defun erc-d-u--read-exchange-slowly (num orig info) + (when-let ((spec (funcall orig info))) + (when (symbolp (car spec)) + (setf spec (copy-sequence spec) + (nth 1 spec) (cond ((functionp num) (funcall num (nth 1 spec))) + ((< num 0) (max (nth 1 spec) (- num))) + (t (+ (nth 1 spec) num))))) + spec)) + +(defun erc-d-u--rewrite-for-slow-mo (num read-info) + "Return READ-INFO with a modified reader. +When NUM is a positive number, delay incoming requests by NUM more +seconds. If NUM is negative, raise insufficient incoming delays to at +least -NUM seconds. If NUM is a function, set each delay to whatever it +returns when called with the existing value." + (let ((orig (erc-d-u-scan-d-f read-info))) + (setf (erc-d-u-scan-d-f read-info) + (apply-partially #'erc-d-u--read-exchange-slowly num orig)) + read-info)) + +(defun erc-d-u--get-remote-port (process) + "Return peer TCP port for client PROCESS. +When absent, just generate an id." + (let ((remote (plist-get (process-contact process t) :remote))) + (if (vectorp remote) + (aref remote (1- (length remote))) + (format "%s:%d" (process-contact process :local) + (logand 1023 (time-convert nil 'integer)))))) + +(defun erc-d-u--format-bind-address (process) + "Return string or (STRING . INT) for bind address of network PROCESS." + (let ((local (process-contact process :local))) + (if (vectorp local) ; inet + (cons (mapconcat #'number-to-string (seq-subseq local 0 -1) ".") + (aref local (1- (length local)))) + local))) + +(defun erc-d-u--unkeyword (plist) + "Return a copy of PLIST with keywords keys converted to non-keywords." + (cl-loop for (key value) on plist by #'cddr + when (keywordp key) + do (setq key (intern (substring (symbol-name key) 1))) + append (list key value))) + +(defun erc-d-u--massage-rx-args (key val) + " Massage val so it's suitable for an `rx-let' binding. +Handle cases in which VAL is ([ARGLIST] RX-FORM) rather than just +RX-FORM. KEY becomes the binding name." + (if (and (listp val) + (cdr val) + (not (cddr val)) + (consp (car val))) + (cons key val) + (list key val))) + +(defvar-local erc-d-u--process-buffer nil + "Beacon for erc-d process buffers. +The server process is usually deleted first, but we may want to examine +the buffer afterward.") + +(provide 'erc-d-u) +;;; 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 @@ +;;; erc-d.el --- A dumb test server for ERC -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see +;; . + +;;; Commentary: + +;; This is a netcat style server for testing ERC. The "d" in the name +;; stands for "daemon" as well as for "dialog" (as well as for "dumb" +;; because this server isn't very smart). It either spits out a +;; canned reply when an incoming request matches the expected regexp +;; or signals an error and dies. The entry point function is +;; `erc-d-run'. +;; +;; Canned scripts, or "dialogs," should be Lisp-Data files containing +;; one or more request/reply forms like this: +;; +;; | ((mode-chan 1.5 "MODE #chan") ; request: tag, expr, regex +;; | (0.1 ":irc.org 324 bob #chan +Cint") ; reply: delay, content +;; | (0.0 ":irc.org 329 bob #chan 12345")) ; reply: ... +;; +;; These are referred to as "exchanges." The first element is a list +;; whose CAR is a descriptive "tag" and whose CDR is an incoming +;; "spec" representing an inbound message from the client. The rest +;; of the exchange is composed of outgoing specs representing +;; server-to-client messages. A tag can be any symbol (ideally unique +;; in the dialog), but a leading tilde means the request should be +;; allowed to arrive out of order (within the allotted time). +;; +;; The first element in an incoming spec is a number indicating the +;; maximum number of seconds to wait for a match before raising an +;; error. The CDR is interpreted as the collective arguments of an +;; `rx' form to be matched against the raw request (stripped of its +;; CRLF line ending). A "string-start" backslash assertion, "\\`", is +;; prepended to all patterns. +;; +;; Similarly, the leading number in an *outgoing* spec indicates how +;; many seconds to wait before sending the line, which is rendered by +;; concatenating the other members after evaluating each in place. +;; CRLF line endings are appended on the way out and should be absent. +;; +;; Recall that IRC is "asynchronous," meaning some flow intervals +;; don't jibe with lockstep request-reply semantics. However, for our +;; purposes, grouping things as [input, output1, ..., outputN] makes +;; sense, even though input and output may be completely unrelated. +;; +;; Template interpolation: +;; +;; A rudimentary templating facility is provided for additional +;; flexibility. However, it's best to keep things simple (even if +;; overly verbose), so others can easily tell what's going on at a +;; glance. If necessary, consult existing tests for examples (grep +;; for the variables `erc-d-tmpl-vars' and `erc-d-match-handlers'). +;; +;; Subprocess or in-process?: +;; +;; Running in-process confers better visibility and easier setup at +;; the cost of additional cleanup and resource wrangling. With a +;; subprocess, cleanup happens by pulling the plug, but configuration +;; means loading a separate file or passing -eval "(forms...)" during +;; invocation. In some cases, a subprocess may be the only option, +;; like when trying to avoid `require'ing this file. +;; +;; Dialog objects: +;; +;; For a given exchange, the first argument passed to a request +;; handler is the `erc-d-dialog' object representing the overall +;; conversation with the connecting peer. It can be used to pass +;; information between handlers during a session. Some important +;; items are: +;; +;; * name (symbol); name of the current dialog +;; +;; * queue (ring); a backlog of unhandled raw requests, minus CRLF +;; endings. +;; +;; * timers (list of timers); when run, these send messages originally +;; deferred as per the most recently matched exchange's delay info. +;; Normally, all outgoing messages must be sent before another request +;; is considered. (See `erc-d--send-outgoing' for an escape hatch.) +;; +;; * hunks (iterator of iterators); unconsumed exchanges as read from +;; a Lisp-Data dialog file. The exchange iterators being dispensed +;; themselves yield portions of member forms as a 2- or 3-part +;; sequence: [tag] spec. (Here, "hunk" just means "list of raw, +;; unrendered exchange elements") +;; +;; * vars (alist of cons pairs); for sharing state among template +;; functions during the lifetime of an exchange. Initially populated +;; by `erc-d-tmpl-vars', these KEY/VALUE pairs are expanded in the +;; templates and optionally updated by "exchange handlers" (see +;; `erc-d-match-handlers'). When VALUE is a function, occurrences of +;; KEY in an outgoing spec are replaced with the result of calling +;; VALUE with match data set appropriately. See +;; `erc-d--render-entries' for details. +;; +;; * exchanges (ring of erc-d-exchange objects); activated hunks +;; allowed to match out of order, plus the current active exchange +;; being yielded from, if any. See `erc-d-exchange'. +;; +;; TODO +;; +;; - Remove un(der)used functionality and simplify API +;; - Maybe migrate d-u and d-i dependencies here + +;;; Code: +(eval-and-compile + (let* ((d (file-name-directory (or (macroexp-file-name) buffer-file-name))) + (load-path (cons (directory-file-name d) load-path))) + (require 'erc-d-i) + (require 'erc-d-u))) + +(require 'ring) + +(defvar erc-d-server-name "erc-d-server" + "Default name of a server process and basis for its buffer name. +Only relevant when starting a server with `erc-d-run'.") + +(defvar erc-d-server-fqdn "irc.example.org" + "Usually the same as the server's RPL_MYINFO \"announced name\". +Possibly used by overriding handlers, like the one for PING, and/or +dialog templates for the sender portion of a reply message.") + +(defvar erc-d-linger-secs nil + "Seconds to wait before quitting for all dialogs. +For more granular control, use the provided LINGER `rx' variable (alone) +as the incoming template spec of a dialog's last exchange.") + +(defvar erc-d-tmpl-vars nil + "An alist of template bindings available to client dialogs. +Populate it when calling `erc-d-run', and the contents will be made +available to all client dialogs through the `erc-d-dialog' \"vars\" +field and (therefore) to all templates as variables when rendering. For +example, a key/value pair like (network . \"oftc\") will cause instances +of the (unquoted) symbol `network' to be replaced with \"oftc\" in the +rendered template string. + +This list provides default template bindings common to all dialogs. +Each new client-connection process makes a shallow copy on init, but the +usual precautions apply when mutating member items. Within the span of +a dialog, updates not applicable to all exchanges should die with their +exchange. See `erc-d--render-entries' for details. In the unlikely +event that an exchange-specific handler is needed, see +`erc-d-match-handlers'.") + +(defvar erc-d-match-handlers nil + "A plist of exchange-tag symbols mapped to request-handler functions. +This is meant to address edge cases for which `erc-d-tmpl-vars' comes up +short. These may include (1) needing access to the client process +itself and/or (2) adding or altering outgoing response templates before +rendering. Note that (2) requires using `erc-d-exchange-rebind' instead +of manipulating exchange bindings directly. + +The hook-like function `erc-d-on-match' calls any handler whose key is +`eq' to the tag of the currently matched exchange (passing the client +`erc-d-dialog' as the first argument and the current `erc-d-exchange' +object as the second). The handler runs just prior to sending the first +response.") + +(defvar erc-d-auto-pong t + "Handle PING requests automatically.") + +(defvar erc-d--in-process t + "Whether the server is running in the same Emacs as ERT.") + +(defvar erc-d--slow-mo nil + "Adjustment for all incoming timeouts. +This is to allow for human interaction or a slow Emacs or CI runner. +The value is the number of seconds to extend all incoming spec timeouts +by on init. If the value is a negative number, it's negated and +interpreted as a lower bound to raise all incoming timeouts to. If the +value is a function, it should take an existing timeout in seconds and +return a replacement.") + +(defconst erc-d--eof-sentinel "__EOF__") +(defconst erc-d--linger-sentinel "__LINGER__") +(defconst erc-d--drop-sentinel "__DROP__") + +(defvar erc-d--clients nil + "List containing all clients for this server session.") + +;; Some :type names may just be made up (not actual CL types) + +(cl-defstruct (erc-d-spec) ; see `erc-d--render-entries' + (head nil :type symbol) ; or number? + (entry nil :type list) + (state 0 :type integer)) + +(cl-defstruct (erc-d-exchange) + "Object representing a request/response unit from a canned dialog." + (dialog nil :type erc-d-dialog) ; owning dialog + (tag nil :type symbol) ; a.k.a. tag, the caar + (pattern nil :type string) ; regexp to match requests against + (inspec nil :type list) ; original unrendered incoming spec + (hunk nil :type erc-d-u-scan-e) ; active raw exchange hunk being yielded + (spec nil :type erc-d-spec) ; active spec, see `erc-d--render-entries' + (timeout nil :type number) ; time allotted for current request + (timer nil :type timer) ; match timer fires when timeout expires + (bindings nil :type list) ; `eval'-style env pairs (KEY . VAL) ... + (rx-bindings nil :type list) ; rx-let bindings + (deferred nil :type boolean) ; whether sender is paused + ;; Post-match + (match-data nil :type match-data) ; from the latest matched request + (request nil :type string)) ; the original request sans CRLF + +(cl-defstruct (erc-d-dialog) + "Session state for managing a client conversation." + (process nil :type process) ; client-connection process + (name nil :type symbol) ; likely the interned stem of the file + (queue nil :type ring) ; backlog of incoming lines to process + (hunks nil :type erc-d-u-scan-d) ; nil when done; info on raw exchange hunks + (timers nil :type list) ; unsent replies + (vars nil :type list) ; template bindings for rendering + (exchanges nil :type ring) ; ring of erc-d-exchange objects + (state nil :type symbol) ; handler's last recorded control state + (matched nil :type erc-d-exchange) ; currently matched exchange + (message nil :type erc-d-i-message) ; `erc-d-i-message' + (match-handlers nil :type list) ; copy of `erc-d-match-handlers' + (server-fqdn nil :type string) ; copy of `erc-d-server-fqdn' + (finalizer nil :type function) ; custom teardown, passed dialog and exchange + ;; Post-match history is a plist whose keys are exchange tags + ;; (symbols) and whose values are a cons of match-data and request + ;; values from prior matches. + (history nil :type list)) + +(defun erc-d--initialize-client (process) + "Initialize state variables used by a client PROCESS." + ;; Discard server-only/owned props + (process-put process :dialog-dialogs nil) + (let* ((server (process-get process :server)) + (reader (pop (process-get server :dialog-dialogs))) + (name (pop reader)) + ;; Copy handlers so they can self-mutate per process + (mat-h (copy-sequence (process-get process :dialog-match-handlers))) + (fqdn (copy-sequence (process-get process :dialog-server-fqdn))) + (vars (copy-sequence (process-get process :dialog-vars))) + (dialog (make-erc-d-dialog :name name + :process process + :queue (make-ring 5) + :exchanges (make-ring 10) + :match-handlers mat-h + :server-fqdn fqdn))) + ;; Add items expected by convenience commands like `erc-d-exchange-reload'. + (setf (alist-get 'EOF vars) `(: ,erc-d--eof-sentinel eot) + (alist-get 'LINGER vars) `(: ,erc-d--linger-sentinel eot) + (alist-get 'DROP vars) `(: ,erc-d--drop-sentinel eot) + (erc-d-dialog-vars dialog) vars + (erc-d-dialog-hunks dialog) reader) + ;; Add reverse link, register client, launch + (process-put process :dialog dialog) + (push process erc-d--clients) + (erc-d--command-refresh dialog nil) + (erc-d--on-request process))) + +(defun erc-d-load-replacement-dialog (dialog replacement &optional skip) + "Find REPLACEMENT among backlog and swap out current DIALOG's iterator. +With int SKIP, advance past that many exchanges." + (let* ((process (erc-d-dialog-process dialog)) + (server (process-get process :server)) + (reader (assoc-default replacement + (process-get server :dialog-dialogs) + #'eq))) + (when skip (while (not (zerop skip)) + (erc-d-u--read-dialog reader) + (cl-decf skip))) + (dolist (timer (erc-d-dialog-timers dialog)) + (cancel-timer timer)) + (dolist (exchange (ring-elements (erc-d-dialog-exchanges dialog))) + (cancel-timer (erc-d-exchange-timer exchange))) + (setf (erc-d-dialog-hunks dialog) reader) + (erc-d--command-refresh dialog nil))) + +(defvar erc-d--m-debug (getenv "ERC_D_DEBUG")) + +(defmacro erc-d--m (process format-string &rest args) + "Output ARGS using FORMAT-STRING somewhere depending on context. +PROCESS should be a client connection or a server network process." + `(let ((format-string (if erc-d--m-debug + (concat (format-time-string "%s.%N: ") + ,format-string) + ,format-string)) + (want-insert (and ,process erc-d--in-process))) + (when want-insert + (with-current-buffer (process-buffer (process-get ,process :server)) + (goto-char (point-max)) + (insert (concat (format ,format-string ,@args) "\n")))) + (when (or erc-d--m-debug (not want-insert)) + (message format-string ,@args)))) + +(defmacro erc-d--log (process string &optional outbound) + "Log STRING sent to (OUTBOUND) or received from PROCESS peer." + `(let ((id (or (process-get ,process :log-id) + (let ((port (erc-d-u--get-remote-port ,process))) + (process-put ,process :log-id port) + port))) + (name (erc-d-dialog-name (process-get ,process :dialog)))) + (if ,outbound + (erc-d--m process "-> %s:%s %s" name id ,string) + (dolist (line (split-string ,string "\r\n")) + (erc-d--m process "<- %s:%s %s" name id line))))) + +(defun erc-d--log-process-event (server process msg) + (erc-d--m server "%s: %s" process (string-trim-right msg))) + +(defun erc-d--send (process string) + "Send STRING to PROCESS peer." + (erc-d--log process string 'outbound) + (process-send-string process (concat string "\r\n"))) + +(define-inline erc-d--fuzzy-p (exchange) + (inline-letevals (exchange) + (inline-quote + (let ((tag (symbol-name (erc-d-exchange-tag ,exchange)))) + (eq ?~ (aref tag 0)))))) + +(define-error 'erc-d-timeout "Timed out awaiting expected request") + +(defun erc-d--finalize-dialog (dialog) + "Delete client-connection and finalize DIALOG. +Return associated server." + (let ((process (erc-d-dialog-process dialog))) + (setq erc-d--clients (delq process erc-d--clients)) + (dolist (timer (erc-d-dialog-timers dialog)) + (cancel-timer timer)) + (dolist (exchange (ring-elements (erc-d-dialog-exchanges dialog))) + (cancel-timer (erc-d-exchange-timer exchange))) + (prog1 (process-get process :server) + (delete-process process)))) + +(defun erc-d--teardown (&optional sig &rest msg) + "Clean up processes and maybe send signal SIG using MSG." + (unless erc-d--in-process + (when sig + (erc-d--m nil "%s %s" sig (apply #'format-message msg))) + (kill-emacs (if msg 1 0))) + (let (process servers) + (while (setq process (pop erc-d--clients)) + (push (erc-d--finalize-dialog (process-get process :dialog)) servers)) + (dolist (server servers) + (delete-process server))) + (dolist (timer timer-list) + (when (memq (timer--function timer) + '(erc-d--send erc-d--command-handle-all)) + (erc-d--m nil "Stray timer found: %S" (timer--function timer)) + (cancel-timer timer))) + (when sig + (dolist (buf erc-d-u--canned-buffers) + (kill-buffer buf)) + (setq erc-d-u--canned-buffers nil) + (signal sig (list (apply #'format-message msg))))) + +(defun erc-d--teardown-this-dialog-at-least (dialog) + "Run `erc-d--teardown' after destroying DIALOG if it's the last one." + (let ((server (process-get (erc-d-dialog-process dialog) :server)) + (us (erc-d-dialog-process dialog))) + (erc-d--finalize-dialog dialog) + (cl-assert (not (memq us erc-d--clients))) + (unless (or (process-get server :dialog-dialogs) + (catch 'other + (dolist (process erc-d--clients) + (when (eq (process-get process :server) server) + (throw 'other process))))) + (push us erc-d--clients) + (erc-d--teardown)))) + +(defun erc-d--expire (dialog exchange) + "Raise timeout error for EXCHANGE. +This will start the teardown for DIALOG." + (setf (erc-d-exchange-spec exchange) nil) + (if-let ((finalizer (erc-d-dialog-finalizer dialog))) + (funcall finalizer dialog exchange) + (erc-d--teardown 'erc-d-timeout "Timed out awaiting request: %s" + (list :name (erc-d-exchange-tag exchange) + :pattern (erc-d-exchange-pattern exchange) + :timeout (erc-d-exchange-timeout exchange) + :dialog (erc-d-dialog-name dialog))))) + +;; Using `run-at-time' here allows test cases to examine replies as +;; they arrive instead of forcing tests to wait until an exchange +;; completes. The `run-at-time' in `erc-d--command-meter-replies' +;; does the same. When running as a subprocess, a normal while loop +;; with a `sleep-for' works fine (including with multiple dialogs). +;; FYI, this issue was still present in older versions that called +;; this directly from `erc-d--filter'. + +(defun erc-d--on-request (process) + "Handle one request for client-connection PROCESS." + (when (process-live-p process) + (let* ((dialog (process-get process :dialog)) + (queue (erc-d-dialog-queue dialog))) + (unless (ring-empty-p queue) + (let* ((parsed (ring-remove queue)) + (cmd (intern (erc-d-i-message.command parsed)))) + (setf (erc-d-dialog-message dialog) parsed) + (erc-d-command dialog cmd))) + (run-at-time nil nil #'erc-d--on-request process)))) + +(defun erc-d--drop-p (exchange) + (memq 'DROP (erc-d-exchange-inspec exchange))) + +(defun erc-d--linger-p (exchange) + (memq 'LINGER (erc-d-exchange-inspec exchange))) + +(defun erc-d--fake-eof (dialog) + "Simulate receiving a fictitious \"EOF\" message from peer." + (setf (erc-d-dialog-message dialog) ; use downcase for internal cmds + (make-erc-d-i-message :command "eof" :unparsed erc-d--eof-sentinel)) + (run-at-time nil nil #'erc-d-command dialog 'eof)) + +(defun erc-d--process-sentinel (process event) + "Set up or tear down client-connection PROCESS depending on EVENT." + (erc-d--log-process-event process process event) + (if (eq 'open (process-status process)) + (erc-d--initialize-client process) + (let* ((dialog (process-get process :dialog)) + (exes (and dialog (erc-d-dialog-exchanges dialog)))) + (if (and exes (not (ring-empty-p exes))) + (cond ((string-prefix-p "connection broken" event) + (erc-d--fake-eof dialog)) + ;; Ignore disconnecting peer when pattern is DROP + ((and (string-prefix-p "deleted" event) + (erc-d--drop-p (ring-ref exes -1)))) + (t (erc-d--teardown))) + (erc-d--teardown))))) + +(defun erc-d--filter (process string) + "Handle input received from peer. +PROCESS represents a client peer connection and STRING is a raw request +including line delimiters." + (let ((queue (erc-d-dialog-queue (process-get process :dialog)))) + (setq string (concat (process-get process :stashed-input) string)) + (while (and string (string-match (rx (+ "\r\n")) string)) + (let ((line (substring string 0 (match-beginning 0)))) + (setq string (unless (= (match-end 0) (length string)) + (substring string (match-end 0)))) + (erc-d--log process line nil) + (ring-insert queue (erc-d-i--parse-message line 'decode)))) + (when string + (setf (process-get process :stashed-input) string)))) + +;; Misc process properties: +;; +;; The server property `:dialog-dialogs' is an alist of (symbol +;; . erc-d-u-scan-d) conses, each of which pairs a dialogs name with +;; info on its read progress (described above in the Commentary). +;; This list is populated by `erc-d-run' at the start of each session. +;; +;; Client-connection processes keep a reference to their server via a +;; `:server' property, which can be used to share info with other +;; clients. There is currently no built-in way to do the same with +;; clients of other servers. Clients also keep references to their +;; dialogs and raw messages via `:dialog' and `:stashed-input'. +;; +;; The logger stores a unique, human-friendly process name in the +;; client-process property `:log-id'. + +(defun erc-d--start (host service name &rest plist) + "Serve canned replies on HOST at SERVICE. +Return the new server process immediately when `erc-d--in-process' is +non-nil. Otherwise, serve forever. PLIST becomes the plist of the +server process and is used to initialize the plists of connection +processes. NAME is used for the process and the buffer." + (let* ((buf (get-buffer-create (concat "*" name "*"))) + (proc (make-network-process :server t + :buffer buf + :noquery t + :filter #'erc-d--filter + :log #'erc-d--log-process-event + :sentinel #'erc-d--process-sentinel + :name name + :family (if host 'ipv4 'local) + :coding 'binary + :service (or service t) + :host host + :plist plist))) + (process-put proc :server proc) + ;; We don't have a minor mode, so use an arbitrary variable to mark + ;; buffers owned by us instead + (with-current-buffer buf (setq erc-d-u--process-buffer t)) + (erc-d--m proc "Starting network process: %S %S" + proc (erc-d-u--format-bind-address proc)) + (if erc-d--in-process + proc + (while (process-live-p proc) + (accept-process-output nil 0.01))))) + +(defun erc-d--wrap-func-val (dialog exchange key func) + "Return a form invoking FUNC when evaluated. +Arrange for FUNC to be called with the args it expects based on +the description in `erc-d--render-entries'." + (let (args) + ;; Ignore &rest or &optional + (pcase-let ((`(,n . ,_) (func-arity func))) + (pcase n + (0) + (1 (push (apply-partially #'erc-d-exchange-multi dialog exchange key) + args)) + (2 (push exchange args) + (push (apply-partially #'erc-d-exchange-multi dialog exchange key) + args)) + (_ (error "Incompatible function: %s" func)))) + (lambda () (apply func args)))) + +(defun erc-d-exchange-reload (dialog exchange) + "Rebuild all bindings for EXCHANGE from those in DIALOG." + (cl-loop for (key . val) in (erc-d-dialog-vars dialog) + unless (keywordp key) + do (push (erc-d-u--massage-rx-args key val) + (erc-d-exchange-rx-bindings exchange)) + when (functionp val) do + (setq val (erc-d--wrap-func-val dialog exchange key val)) + do (push (cons key val) (erc-d-exchange-bindings exchange)))) + +(defun erc-d-exchange-rebind (dialog exchange key val &optional export) + "Modify a binding between renders. + +Bind symbol KEY to VAL, replacing whatever existed before, which may +have been a function. A third, optional argument, if present and +non-nil, results in the DIALOG's bindings for all EXCHANGEs adopting +this binding. VAL can either be a function of the type described in +`erc-d--render-entries' or any value acceptable as an argument to the +function `concat'. + +DIALOG and EXCHANGE are the current `erc-d-dialog' and `erc-d-exchange' +objects for the request context." + (when export + (setf (alist-get key (erc-d-dialog-vars dialog)) val)) + (if (functionp val) + (setf (alist-get key (erc-d-exchange-bindings exchange)) + (erc-d--wrap-func-val dialog exchange key val)) + (setf (alist-get key (erc-d-exchange-rx-bindings exchange)) (list val) + (alist-get key (erc-d-exchange-bindings exchange)) val)) + val) + +(defun erc-d-exchange-match (exchange match-number &optional tag) + "Return match portion of current or previous request. +MATCH-NUMBER is the match group number. TAG, if provided, means the +exchange tag (name) from some previously matched request." + (if tag + (pcase-let* ((dialog (erc-d-exchange-dialog exchange)) + (`(,m-d . ,req) (plist-get (erc-d-dialog-history dialog) + tag))) + (set-match-data m-d) + (match-string match-number req)) + (match-string match-number (erc-d-exchange-request exchange)))) + +(defun erc-d-exchange-multi (dialog exchange key cmd &rest args) + "Call CMD with ARGS. +This is a utility passed as the first argument to all template +functions. DIALOG and EXCHANGE are pre-applied. A few pseudo +commands, like `:request', are provided for convenience so that +the caller's definition doesn't have to include this file. The +rest are access and mutation utilities, such as `:set', which +assigns KEY a new value, `:get-binding', which looks up KEY in +`erc-d-exchange-bindings', and `:get-var', which looks up KEY in +`erc-d-dialog-vars'." + (pcase cmd + (:set (apply #'erc-d-exchange-rebind dialog exchange key args)) + (:reload (apply #'erc-d-exchange-reload dialog exchange args)) + (:rebind (apply #'erc-d-exchange-rebind dialog exchange args)) + (:match (apply #'erc-d-exchange-match exchange args)) + (:request (erc-d-exchange-request exchange)) + (:match-data (erc-d-exchange-match-data exchange)) + (:dialog-name (erc-d-dialog-name dialog)) + (:get-binding (cdr (assq (car args) (erc-d-exchange-bindings exchange)))) + (:get-var (alist-get (car args) (erc-d-dialog-vars dialog))))) + +(defun erc-d--render-incoming-entry (exchange spec) + (let ((rx--local-definitions (rx--extend-local-defs + (erc-d-exchange-rx-bindings exchange)))) + (rx-to-string `(: bos ,@(erc-d-spec-entry spec)) 'no-group))) + +(defun erc-d--render-outgoing-entry (exchange entry) + (let (out this) + (while (setq this (pop entry)) + (set-match-data (erc-d-exchange-match-data exchange)) + (unless (stringp this) + (cl-assert (symbolp this)) + (setq this (or (alist-get this (erc-d-exchange-bindings exchange)) + (symbol-value this))) + ;; Allow reference to overlong var name unbecoming of a template + (when this + (when (symbolp this) (setq this (symbol-value this))) + (when (functionp this) (setq this (save-match-data (funcall this)))) + (unless (stringp this) (error "Unexpected token %S" this)))) + (push this out)) + (apply #'concat (nreverse out)))) + +(defun erc-d--render-entries (exchange &optional yield-result) + "Act as an iterator producing rendered strings from EXCHANGE hunks. +When an entry's CAR is an arbitrary symbol, yield that back first, and +consider the entry an \"incoming\" entry. Then, regardless of the +entry's type (incoming or outgoing), yield back the next element, which +should be a number representing either a timeout (incoming) or a +delay (outgoing). After that, yield a rendered template (outgoing) or a +regular expression (incoming); both should be treated as immutable. + +When evaluating a template, bind the keys in the alist stored in the +dialog's `vars' field to its values, but skip any self-quoters, like +:foo. When an entry is incoming, replace occurrences of a key with its +value, which can be any valid `rx' form (see Info node `(elisp) +Extending Rx'). Do the same when an entry is outgoing, but expect a +value's form to be (anything that evaluates to) something acceptable by +`concat' or, alternatively, a function that returns a string or nil. + +Repeat the last two steps for the remaining entries, all of which are +assumed to be outgoing. That is, continue yielding a timeout/delay and +a rendered string for each entry, and yield nil when exhausted. + +Once again, for an incoming entry, the yielded string is a regexp to be +matched against the raw request. For outgoing, it's the final response, +ready to be sent out (after adding the appropriate line ending). + +To help with testing, bindings are not automatically created from +DIALOG's \"vars\" alist when this function is invoked. But this can be +forced by sending a non-nil YIELD-RESULT into the generator on the +second \"next\" invocation of a given iteration. This clobbers any +temporary bindings that don't exist in the DIALOG's `vars' alist, such +as those added via `erc-d-exchange-rebind' (unless \"exported\"). + +As noted earlier, template symbols can be bound to functions. When +called during rendering, the match data from the current (matched) +request is accessible by calling the function `match-data'. + +A function may ask for up to two required args, which are provided as +needed. When applicable, the first required arg is a `funcall'-able +helper that accepts various keyword-based commands, like :rebind, and a +variable number of args. See `erc-d-exchange-multi' for details. When +specified, the second required arg is the current `erc-d-exchange' +object, which has among its members its owning `erc-d-dialog' object. +This should suffice as a safety valve for any corner-case needs. +Non-required args are ignored." + (let ((spec (erc-d-exchange-spec exchange)) + (dialog (erc-d-exchange-dialog exchange)) + (entries (erc-d-exchange-hunk exchange))) + (unless (erc-d-spec-entry spec) + (setf (erc-d-spec-entry spec) (erc-d-u--read-exchange entries))) + (catch 'yield + (while (erc-d-spec-entry spec) + (pcase (erc-d-spec-state spec) + (0 (cl-incf (erc-d-spec-state spec)) + (throw 'yield (setf (erc-d-spec-head spec) + (pop (erc-d-spec-entry spec))))) + (1 (cl-incf (erc-d-spec-state spec)) + (when yield-result + (erc-d-exchange-reload dialog exchange)) + (unless (numberp (erc-d-spec-head spec)) + (setf (erc-d-exchange-inspec exchange) (erc-d-spec-entry spec)) + (throw 'yield + (prog1 (pop (erc-d-spec-entry spec)) + (setf (erc-d-spec-entry spec) + (erc-d--render-incoming-entry exchange spec)))))) + (2 (setf (erc-d-spec-state spec) 0) + (throw 'yield + (let ((entry (erc-d-spec-entry spec))) + (setf (erc-d-spec-entry spec) nil) + (if (stringp entry) + entry + (erc-d--render-outgoing-entry exchange entry)))))))))) + +(defun erc-d--iter (exchange) + (apply-partially #'erc-d--render-entries exchange)) + +(defun erc-d-on-match (dialog exchange) + "Handle matched exchange request. +Allow the first handler in `erc-d-match-handlers' whose key matches TAG +to manipulate replies before they're sent to the DIALOG peer." + (when-let* ((tag (erc-d-exchange-tag exchange)) + (handler (plist-get (erc-d-dialog-match-handlers dialog) tag))) + (let ((md (erc-d-exchange-match-data exchange))) + (set-match-data md) + (funcall handler dialog exchange)))) + +(defun erc-d--send-outgoing (dialog exchange) + "Send outgoing lines for EXCHANGE to DIALOG peer. +Assume the next spec is outgoing. If its delay value is zero, render +the template and send the resulting message straight away. Do the same +when DELAY is negative, only arrange for its message to be sent (abs +DELAY) seconds later, and then keep on processing. If DELAY is +positive, pause processing and yield DELAY." + (let ((specs (erc-d--iter exchange)) + (process (erc-d-dialog-process dialog)) + (deferred (erc-d-exchange-deferred exchange)) + delay) + ;; Could stash/pass thunk instead to ensure specs can't be mutated + ;; between calls (by temporarily replacing dialog member with a fugazi) + (when deferred + (erc-d--send process (funcall specs)) + (setf deferred nil (erc-d-exchange-deferred exchange) deferred)) + (while (and (not deferred) (setq delay (funcall specs))) + (cond ((zerop delay) (erc-d--send process (funcall specs))) + ((< delay 0) (push (run-at-time (- delay) nil #'erc-d--send + process (funcall specs)) + (erc-d-dialog-timers dialog))) + ((setf deferred t (erc-d-exchange-deferred exchange) deferred)))) + delay)) + +(defun erc-d--add-dialog-linger (dialog exchange) + "Add finalizer for EXCHANGE in DIALOG." + (erc-d--m (erc-d-dialog-process dialog) + "Lingering for %.2f seconds" (erc-d-exchange-timeout exchange)) + (let ((start (current-time))) + (setf (erc-d-dialog-finalizer dialog) + (lambda (&rest _) + (erc-d--m (erc-d-dialog-process dialog) + "Lingered for %.2f seconds" + (float-time (time-subtract (current-time) start))) + (erc-d--teardown-this-dialog-at-least dialog))))) + +(defun erc-d--add-dialog-drop (dialog exchange) + "Add finalizer for EXCHANGE in DIALOG." + (erc-d--m (erc-d-dialog-process dialog) + "Dropping in %.2f seconds" (erc-d-exchange-timeout exchange)) + (setf (erc-d-dialog-finalizer dialog) + (lambda (&rest _) + (erc-d--m (erc-d-dialog-process dialog) + "Dropping %S" (erc-d-dialog-name dialog)) + (erc-d--finalize-dialog dialog)))) + +(defun erc-d--create-exchange (dialog hunk) + "Initialize next exchange HUNK for DIALOG." + (let* ((spec (make-erc-d-spec)) + (exchange (make-erc-d-exchange :dialog dialog :hunk hunk :spec spec)) + (specs (erc-d--iter exchange))) + (setf (erc-d-exchange-tag exchange) (funcall specs) + (erc-d-exchange-timeout exchange) (funcall specs t) + (erc-d-exchange-pattern exchange) (funcall specs)) + (cond ((erc-d--linger-p exchange) + (erc-d--add-dialog-linger dialog exchange)) + ((erc-d--drop-p exchange) + (erc-d--add-dialog-drop dialog exchange))) + (setf (erc-d-exchange-timer exchange) + (run-at-time (erc-d-exchange-timeout exchange) + nil #'erc-d--expire dialog exchange)) + exchange)) + +(defun erc-d--command-consider-prep-fail (dialog line exes) + (list 'error "Match failed: %S %S" line + (list :exes (mapcar #'erc-d-exchange-pattern + (ring-elements exes)) + :dialog (erc-d-dialog-name dialog)))) + +(defun erc-d--command-consider-prep-success (dialog line exes matched) + (setf (erc-d-exchange-request matched) line + (erc-d-exchange-match-data matched) (match-data) + ;; Also add current to match history, indexed by exchange tag + (plist-get (erc-d-dialog-history dialog) + (erc-d-exchange-tag matched)) + (cons (match-data) line)) ; do we need to make a copy of this? + (cancel-timer (erc-d-exchange-timer matched)) + (ring-remove exes (ring-member exes matched))) + +(cl-defun erc-d--command-consider (dialog) + "Maybe return next matched exchange for DIALOG. +Upon encountering a mismatch, return an error of the form (ERROR-SYMBOL +DATA). But when only fuzzies remain in the exchange pool, return nil." + (let* ((parsed (erc-d-dialog-message dialog)) + (line (erc-d-i-message.unparsed parsed)) + (exes (erc-d-dialog-exchanges dialog)) + ;; + matched) + (let ((elts (ring-elements exes))) + (while (and (setq matched (pop elts)) + (not (string-match (erc-d-exchange-pattern matched) line))) + (if (and (not elts) (erc-d--fuzzy-p matched)) + ;; Nothing to do, so advance + (cl-return-from erc-d--command-consider nil) + (cl-assert (or (not elts) (erc-d--fuzzy-p matched)))))) + (if matched + (erc-d--command-consider-prep-success dialog line exes matched) + (erc-d--command-consider-prep-fail dialog line exes)))) + +(defun erc-d--active-ex-p (ring) + "Return non-nil when RING has a non-fuzzy exchange. +That is, return nil when RING is empty or when it only has exchanges +with leading-tilde tags." + (let ((i 0) + (len (ring-length ring)) + ex found) + (while (and (not found) (< i len)) + (unless (erc-d--fuzzy-p (setq ex (ring-ref ring i))) + (setq found ex)) + (cl-incf i)) + found)) + +(defun erc-d--finalize-done (dialog) + ;; Linger logic for individual dialogs is handled elsewhere + (if-let ((finalizer (erc-d-dialog-finalizer dialog))) + (funcall finalizer dialog) + (let ((d (process-get (erc-d-dialog-process dialog) :dialog-linger-secs))) + (push (run-at-time d nil #'erc-d--teardown) + (erc-d-dialog-timers dialog))))) + +(defun erc-d--advance-or-die (dialog) + "Govern the lifetime of DIALOG. +Replenish exchanges from reader and insert them into the pool of +expected matches, as produced. Return a symbol indicating session +status: deferring, matching, depleted, or done." + (let ((exes (erc-d-dialog-exchanges dialog)) + hunk) + (cond ((erc-d--active-ex-p exes) 'deferring) + ((setq hunk (erc-d-u--read-dialog (erc-d-dialog-hunks dialog))) + (let ((exchange (erc-d--create-exchange dialog hunk))) + (if (erc-d--fuzzy-p exchange) + (ring-insert exes exchange) + (ring-insert-at-beginning exes exchange))) + 'matching) + ((not (ring-empty-p exes)) 'depleted) + (t 'done)))) + +(defun erc-d--command-meter-replies (dialog exchange &optional cmd) + "Ignore requests until all replies have been sent. +Do this for some previously matched EXCHANGE in DIALOG based on CMD, a +symbol. As a side effect, maybe schedule the resumption of the main +loop after some delay." + (let (delay) + (if (or (not cmd) (eq 'resume cmd)) + (when (setq delay (erc-d--send-outgoing dialog exchange)) + (push (run-at-time delay nil #'erc-d--command-handle-all + dialog 'resume) + (erc-d-dialog-timers dialog)) + (erc-d-dialog-state dialog)) + (setf (erc-d-dialog-state dialog) 'sending)))) + +(defun erc-d--die-unexpected (dialog) + (erc-d--teardown 'error "Received unexpected input: %S" + (erc-d-i-message.unparsed (erc-d-dialog-message dialog)))) + +(defun erc-d--command-refresh (dialog matched) + (let ((state (erc-d--advance-or-die dialog))) + (when (eq state 'done) + (erc-d--finalize-done dialog)) + (unless matched + (when (eq state 'depleted) + (erc-d--die-unexpected dialog)) + (cl-assert (memq state '(matching depleted)) t)) + (setf (erc-d-dialog-state dialog) state))) + +(defun erc-d--command-handle-all (dialog cmd) + "Create handler to act as control agent and process DIALOG requests. +Have it ingest internal control commands (lowercase symbols) and yield +back others indicating the lifecycle stage of the current dialog." + (let ((matched (erc-d-dialog-matched dialog))) + (cond + (matched + (or (erc-d--command-meter-replies dialog matched cmd) + (setf (erc-d-dialog-matched dialog) nil) + (erc-d--command-refresh dialog t))) + ((pcase cmd ; FIXME remove command facility or make extensible + ('resume nil) + ('eof (erc-d--m (erc-d-dialog-process dialog) "Received an EOF") nil))) + (t ; matching + (setq matched nil) + (catch 'yield + (while (not matched) + (when (ring-empty-p (erc-d-dialog-exchanges dialog)) + (erc-d--die-unexpected dialog)) + (when (setq matched (erc-d--command-consider dialog)) + (if (eq (car-safe matched) 'error) + (apply #'erc-d--teardown matched) + (erc-d-on-match dialog matched) + (setf (erc-d-dialog-matched dialog) matched) + (if-let ((s (erc-d--command-meter-replies dialog matched nil))) + (throw 'yield s) + (setf (erc-d-dialog-matched dialog) nil)))) + (erc-d--command-refresh dialog matched))))))) + +;;;; Handlers for IRC commands + +(cl-defgeneric erc-d-command (dialog cmd) + "Handle new CMD from client for DIALOG. +By default, defer to this dialog's `erc-d--command-handle-all' instance, +which is stored in its `handler' field.") + +(cl-defmethod erc-d-command ((dialog erc-d-dialog) cmd) + (when (eq 'sending (erc-d--command-handle-all dialog cmd)) + (ring-insert-at-beginning (erc-d-dialog-queue dialog) + (erc-d-dialog-message dialog)))) + +;; A similar PONG handler would be useless because we know when to +;; expect them + +(cl-defmethod erc-d-command ((dialog erc-d-dialog) (_cmd (eql PING)) + &context (erc-d-auto-pong (eql t))) + "Respond to PING request from DIALOG peer when ERC-D-AUTO-PONG is t." + (let* ((parsed (erc-d-dialog-message dialog)) + (process (erc-d-dialog-process dialog)) + (nonce (car (erc-d-i-message.command-args parsed))) + (fqdn (erc-d-dialog-server-fqdn dialog))) + (erc-d--send process (format ":%s PONG %s :%s" fqdn fqdn nonce)))) + + +;;;; Entry points + +(defun erc-d-run (host service &optional server-name &rest dialogs) + "Start serving DIALOGS on HOST at SERVICE. +Pass HOST and SERVICE directly to `make-network-process'. When present, +use string SERVER-NAME for the server-process name as well as that of +its buffer (w. surrounding asterisks). When absent, do the same with +`erc-d-server-name'. When running \"in process,\" return the server +process, otherwise sleep for the duration of the server process. + +A dialog must be a symbol matching the base name of a dialog file in +`erc-d-u-canned-dialog-dir'. + +The variable `erc-d-tmpl-vars' determines the common members of the +`erc-d--render-entries' ENTRIES param. Variables `erc-d-server-fqdn' +and `erc-d-linger-secs' determine the `erc-d-dialog' items +`:server-fqdn' and `:linger-secs' for all client processes. + +The variable `erc-d-tmpl-vars' can be used to initialize the +process's `erc-d-dialog' vars item." + (when (and server-name (symbolp server-name)) + (push server-name dialogs) + (setq server-name nil)) + (let (loaded) + (dolist (dialog (nreverse dialogs)) + (let ((reader (erc-d-u--canned-load-dialog dialog))) + (when erc-d--slow-mo + (setq reader (erc-d-u--rewrite-for-slow-mo erc-d--slow-mo reader))) + (push (cons (erc-d-u--normalize-canned-name dialog) reader) loaded))) + (setq dialogs loaded)) + (erc-d--start host service (or server-name erc-d-server-name) + :dialog-dialogs dialogs + :dialog-vars erc-d-tmpl-vars + :dialog-linger-secs erc-d-linger-secs + :dialog-server-fqdn erc-d-server-fqdn + :dialog-match-handlers (erc-d-u--unkeyword + erc-d-match-handlers))) + +(defun erc-d-serve () + "Start serving canned dialogs from the command line. +Although not autoloaded, this function is meant to be summoned via the +Emacs -f flag while starting a batch session. It prints incoming and +outgoing messages to standard out. + +The main options are --host HOST and --port PORT, which default to +localhost and auto, respectively. The args are the dialogs to run. +Unlike with `erc-d-run', dialogs here *must* be files, meaning Lisp-Data +files adhering to the required format. (These consist of \"specs\" +detailing timing and template info; see commentary for specifics.) + +An optional --add-time N option can also be passed to hike up timeouts +by some number of seconds N. For example, you might run: + + $ emacs -Q -batch -L . \\ + > -l erc-d.el \\ + > -f erc-d-serve \\ + > --host 192.168.124.1 \\ + > --port 16667 \\ + > --add-time 10 \\ + > ./my-dialog.eld + +from a Makefile or manually with \\\\[compile]. And then in +another terminal, do: + + $ nc -C 192.168.124.1 16667 ; or telnet if your nc doesn't have -C + > PASS changeme + ... + +Use `erc-d-run' instead to start the server from within Emacs." + (unless noninteractive + (error "Command-line func erc-d-serve not run in -batch session")) + (setq erc-d--in-process nil) + (let (port host dialogs erc-d--slow-mo) + (while command-line-args-left + (pcase (pop command-line-args-left) + ("--add-time" (setq erc-d--slow-mo + (string-to-number (pop command-line-args-left)))) + ("--linger" (setq erc-d-linger-secs + (string-to-number (pop command-line-args-left)))) + ("--host" (setq host (pop command-line-args-left))) + ("--port" (setq port (string-to-number (pop command-line-args-left)))) + (dialog (push dialog dialogs)))) + (setq dialogs (mapcar #'erc-d-u--massage-canned-name dialogs)) + (when erc-d--slow-mo + (message "Slow mo is ON")) + (apply #'erc-d-run (or host "localhost") port nil (nreverse dialogs)))) + +(provide 'erc-d) + +;;; 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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester") + (0 ":irc.example.org 002 tester :Your host is irc.example.org") + (0 ":irc.example.org 003 tester :This server was created just now") + (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + ;; Just to mix thing's up (force handler to schedule timer) + (0.1 ":irc.example.org 252 tester 0 :IRC Operators online") + (0 ":irc.example.org 253 tester 0 :unregistered connections") + (0 ":irc.example.org 254 tester 1 :channels formed") + (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers") + (0.1 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3") + (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3") + (0 ":irc.example.org 422 tester :MOTD File is missing")) + +((mode-user 5 "MODE tester +i") + (0 ":irc.example.org 221 tester +Zi") + (0 ":irc.example.org 306 tester :You have been marked as being away") + (0 ":tester!~tester@localhost JOIN #chan") + (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.example.org 366 alice #chan :End of NAMES list")) + +;; Some comment (to prevent regression) +((mode-chan 1.2 "MODE #chan") + (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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS :changeme")) + +((~fake 3.2 "FAKE ") + (0.1 ":irc.example.org FAKE irc.example.com :ok")) + +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0 ":irc.example.org 001 tester :Welcome to the Internet tester") + (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 @@ +;; -*- mode: lisp-data; -*- +((pass 1 "PASS " (? ?:) "a") + (0 "hi")) +((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 @@ +;; -*- mode: lisp-data; -*- +((pass 1 "PASS " (? ?:) "b") + (0 "hi")) +((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 @@ +;;; -*- mode: lisp-data -*- +((fake 0 "FAKE noop")) + +((nick 1.2 "NICK tester")) + +((user 2.2 "USER user 0 * :tester") + (0. ":irc.barnet.org 001 tester :Welcome to the BAR Network tester") + (0. ":irc.barnet.org 002 tester :Your host is irc.barnet.org") + (0. ":irc.barnet.org 003 tester :This server was created just now") + (0. ":irc.barnet.org 004 tester irc.barnet.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0. ":irc.barnet.org 005 tester MODES NETWORK=BarNet NICKLEN=32 PREFIX=(qaohv)~&@%+ :are supported by this server") + (0. ":irc.barnet.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0. ":irc.barnet.org 252 tester 0 :IRC Operators online") + (0. ":irc.barnet.org 253 tester 0 :unregistered connections") + (0. ":irc.barnet.org 254 tester 1 :channels formed") + (0. ":irc.barnet.org 255 tester :I have 3 clients and 0 servers") + (0. ":irc.barnet.org 265 tester 3 3 :Current local users 3, max 3") + (0. ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3") + (0. ":irc.barnet.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0. ":irc.barnet.org 221 tester +Zi") + (0. ":irc.barnet.org 306 tester :You have been marked as being away") + (0 ":tester!~u@awyxgybtkx7uq.irc JOIN #chan") + (0 ":irc.barnet.org 353 joe = #chan :+joe!~joe@example.com @%+mike!~mike@example.org") + (0 ":irc.barnet.org 366 joe #chan :End of NAMES list")) + +((mode 1 "MODE #chan") + (0 ":irc.barnet.org 324 tester #chan +nt") + (0 ":irc.barnet.org 329 tester #chan 1620805269") + (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.") + (0.05 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :joe: As he regards his aged father's life.") + (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 @@ +;;; -*- mode: lisp-data -*- + +((nick 1.2 "NICK tester")) + +((user 2.2 "USER user 0 * :tester") + (0. ":irc.foonet.org 001 tester :Welcome to the FOO Network tester") + (0. ":irc.foonet.org 002 tester :Your host is irc.foonet.org") + (0. ":irc.foonet.org 003 tester :This server was created just now") + (0. ":irc.foonet.org 004 tester irc.foonet.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0. ":irc.foonet.org 005 tester MODES NETWORK=FooNet NICKLEN=32 PREFIX=(qaohv)~&@%+ :are supported by this server") + (0. ":irc.foonet.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0. ":irc.foonet.org 252 tester 0 :IRC Operators online") + (0. ":irc.foonet.org 253 tester 0 :unregistered connections") + (0. ":irc.foonet.org 254 tester 1 :channels formed") + (0. ":irc.foonet.org 255 tester :I have 3 clients and 0 servers") + (0. ":irc.foonet.org 265 tester 3 3 :Current local users 3, max 3") + (0. ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3") + (0. ":irc.foonet.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0. ":irc.foonet.org 221 tester +Zi") + (0. ":irc.foonet.org 306 tester :You have been marked as being away") + (0 ":tester!~u@awyxgybtkx7uq.irc JOIN #chan") + (0 ":irc.foonet.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.foonet.org 366 alice #chan :End of NAMES list")) + +((mode 2 "MODE #chan") + (0 ":irc.foonet.org 324 tester #chan +nt") + (0 ":irc.foonet.org 329 tester #chan 1620805269") + (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.") + (0.05 ":bob!~u@awyxgybtkx7uq.irc PRIVMSG #chan :alice: As he regards his aged father's life.") + (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 @@ +;;; -*- mode: lisp-data -*- +((pass 10.0 "PASS " (? ?:) token ":changeme")) + +((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 @@ +;;; -*- mode: lisp-data -*- +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 2.2 "NICK tester")) + +((user 2.2 "USER " user " " (ignored digit "*") " :" realname) + (0.0 ":" dom " 001 " nick " :Welcome to the Internet Relay Network tester") + (0.0 ":" dom " 002 " nick " :Your host is " dom) + (0.0 ":" dom " 003 " nick " :This server was created just now") + (0.0 ":" dom " 004 " nick " " dom " BERios CEIRabehiklmnoqstv Iabehkloqv") + (0.0 ":" dom " 005 " nick " MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0.0 ":" dom " 251 " nick " :There are 3 users and 0 invisible on 1 server(s)") + (0.0 ":" dom " 252 " nick " 0 :IRC Operators online") + (0.0 ":" dom " 253 " nick " 0 :unregistered connections") + (0.0 ":" dom " 254 " nick " 1 :channels formed") + (0.0 ":" dom " 255 " nick " :I have 3 clients and 0 servers") + (0.0 ":" dom " 265 " nick " 3 3 :Current local users 3, max 3") + (0.0 ":" dom " 266 " nick " 3 3 :Current global users 3, max 3") + (0.0 ":" dom " 422 " nick " :MOTD File is missing")) + +((mode-user 2.2 "MODE tester +i") + (0.0 ":" dom " 221 " nick " +Zi") + + (0.0 ":" dom " 306 " nick " :You have been marked as being away") + (0.0 ":" nick "!~" nick "@localhost JOIN #chan") + (0.0 ":" dom " 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org") + (0.0 ":" dom " 366 alice #chan :End of NAMES list")) + +((mode 2.2 "MODE #chan") + (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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester") + (0 ":irc.example.org 002 tester :Your host is irc.example.org") + (0 ":irc.example.org 003 tester :This server was created just now") + (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + ;; Just to mix thing's up (force handler to schedule timer) + (0.1 ":irc.example.org 252 tester 0 :IRC Operators online") + (0 ":irc.example.org 253 tester 0 :unregistered connections") + (0 ":irc.example.org 254 tester 1 :channels formed") + (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers") + (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3") + (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3") + (0 ":irc.example.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0 ":irc.example.org 221 tester +Zi") + (0 ":irc.example.org 306 tester :You have been marked as being away") + (0 ":tester!~tester@localhost JOIN #chan") + (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.example.org 366 alice #chan :End of NAMES list")) + +((mode-chan 1.2 "MODE #chan") + (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey")) + +((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 @@ +;;; -*- mode: lisp-data -*- +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.5 "USER user 0 * :tester") + (0.0 "@time=" now " :irc.org 001 tester :Welcome to the Internet Relay Network tester") + (0.0 "@time=" now " :irc.org 002 tester :Your host is irc.org") + (0.0 "@time=" now " :irc.org 003 tester :This server was created just now") + (0.0 "@time=" now " :irc.org 004 tester irc.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0.0 "@time=" now " :irc.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+ :are supported by this server") + (0.0 "@time=" now " :irc.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0.0 "@time=" now " :irc.org 252 tester 0 :IRC Operators online") + (0.0 "@time=" now " :irc.org 253 tester 0 :unregistered connections") + (0.0 "@time=" now " :irc.org 254 tester 1 :channels formed") + (0.0 "@time=" now " :irc.org 255 tester :I have 3 clients and 0 servers") + (0.0 "@time=" now " :irc.org 265 tester 3 3 :Current local users 3, max 3") + (0.0 "@time=" now " :irc.org 266 tester 3 3 :Current global users 3, max 3") + (0.0 "@time=" now " :irc.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0.0 "@time=" now " :irc.org 221 tester +Zi") + (0.0 "@time=" now " :irc.org 306 tester :You have been marked as being away")) + +((~join-foo 3.2 "JOIN #foo") + (0 "@time=" now " :tester!~tester@localhost JOIN #foo") + (0 "@time=" now " :irc.example.org 353 alice = #foo :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 "@time=" now " :irc.example.org 366 alice #foo :End of NAMES list")) + +((~join-bar 1.2 "JOIN #bar") + (0 "@time=" now " :tester!~tester@localhost JOIN #bar") + (0 "@time=" now " :irc.example.org 353 alice = #bar :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 "@time=" now " :irc.example.org 366 alice #bar :End of NAMES list")) + +((~mode-foo 3.2 "MODE #foo") + (0.0 "@time=" now " :irc.example.org 324 tester #foo +Cint") + (0.0 "@time=" now " :irc.example.org 329 tester #foo 1519850102") + (0.1 "@time=" now " :bob!~bob@example.org PRIVMSG #foo :hey")) + +((mode-bar 10.2 "MODE #bar") + (0.0 "@time=" now " :irc.example.org 324 tester #bar +HMfnrt 50:5h :10:5") + (0.0 "@time=" now " :irc.example.org 329 tester #bar :1602642829") + (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 @@ +;;; -*- mode: lisp-data -*- +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0.0 ":irc.foo.net 001 tester :Welcome to the Internet Relay Network tester") + (0.0 ":irc.foo.net 002 tester :Your host is irc.foo.net") + (0.0 ":irc.foo.net 003 tester :This server was created just now") + (0.0 ":irc.foo.net 004 tester irc.foo.net BERios CEIRabehiklmnoqstv Iabehkloqv") + (0.0 ":irc.foo.net 005 tester MODES NETWORK=FooNet NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0.0 ":irc.foo.net 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0.0 ":irc.foo.net 252 tester 0 :IRC Operators online") + (0.0 ":irc.foo.net 253 tester 0 :unregistered connections") + (0.0 ":irc.foo.net 254 tester 1 :channels formed") + (0.0 ":irc.foo.net 255 tester :I have 3 clients and 0 servers") + (0.0 ":irc.foo.net 265 tester 3 3 :Current local users 3, max 3") + (0.0 ":irc.foo.net 266 tester 3 3 :Current global users 3, max 3") + (0.0 ":irc.foo.net 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0.0 ":irc.foo.net 221 tester +Zi") + (0.0 ":irc.foo.net 306 tester :You have been marked as being away")) + +((join 3 "JOIN #foo") + (0 ":tester!~tester@localhost JOIN #foo") + (0 ":irc.foo.net 353 alice = #foo :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.foo.net 366 alice #foo :End of NAMES list")) + +((mode 3 "MODE #foo") + (0.0 ":irc.foo.net 324 tester #foo +Cint") + (0.0 ":irc.foo.net 329 tester #foo 1519850102") + (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defence, by mercy, 'tis most just.") + (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: Grows, lives, and dies, in single blessedness.") + (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :Look for me.") + (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.") + (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: That I must love a loathed enemy.") + (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: As't please your lordship: I'll leave you.") + (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.") + (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.") + (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :Done")) + +((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 @@ +;;; -*- mode: lisp-data; -*- + +;; https://github.com/DanielOaks/irc-parser-tests +((mask-match + (tests + ((mask . "*@127.0.0.1") + (matches "coolguy!ab@127.0.0.1" "cooldud3!~bc@127.0.0.1") + (fails "coolguy!ab@127.0.0.5" "cooldud3!~d@124.0.0.1")) + ((mask . "cool*@*") + (matches "coolguy!ab@127.0.0.1" "cooldud3!~bc@127.0.0.1" "cool132!ab@example.com") + (fails "koolguy!ab@127.0.0.5" "cooodud3!~d@124.0.0.1")) + ((mask . "cool!*@*") + (matches "cool!guyab@127.0.0.1" "cool!~dudebc@127.0.0.1" "cool!312ab@example.com") + (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")) + ((mask . "cool!?username@*") + (matches "cool!ausername@127.0.0.1" "cool!~username@127.0.0.1") + (fails "cool!username@127.0.0.1")) + ((mask . "cool!a?*@*") + (matches "cool!ab@127.0.0.1" "cool!abc@127.0.0.1") + (fails "cool!a@127.0.0.1")) + ((mask . "cool[guy]!*@*") + (matches "cool[guy]!guy@127.0.0.1" "cool[guy]!a@example.com") + (fails "coolg!ab@127.0.0.1" "cool[!ac@127.0.1.1")))) + (msg-join + (tests + ((desc . "Simple test with verb and params.") + (atoms + (verb . "foo") + (params "bar" "baz" "asdf")) + (matches "foo bar baz asdf" "foo bar baz :asdf")) + ((desc . "Simple test with source and no params.") + (atoms + (source . "src") + (verb . "AWAY")) + (matches ":src AWAY")) + ((desc . "Simple test with source and empty trailing param.") + (atoms + (source . "src") + (verb . "AWAY") + (params "")) + (matches ":src AWAY :")) + ((desc . "Simple test with source.") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" "asdf")) + (matches ":coolguy foo bar baz asdf" ":coolguy foo bar baz :asdf")) + ((desc . "Simple test with trailing param.") + (atoms + (verb . "foo") + (params "bar" "baz" "asdf quux")) + (matches "foo bar baz :asdf quux")) + ((desc . "Simple test with empty trailing param.") + (atoms + (verb . "foo") + (params "bar" "baz" "")) + (matches "foo bar baz :")) + ((desc . "Simple test with trailing param containing colon.") + (atoms + (verb . "foo") + (params "bar" "baz" ":asdf")) + (matches "foo bar baz ::asdf")) + ((desc . "Test with source and trailing param.") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" "asdf quux")) + (matches ":coolguy foo bar baz :asdf quux")) + ((desc . "Test with trailing containing beginning+end whitespace.") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" " asdf quux ")) + (matches ":coolguy foo bar baz : asdf quux ")) + ((desc . "Test with trailing containing what looks like another trailing param.") + (atoms + (source . "coolguy") + (verb . "PRIVMSG") + (params "bar" "lol :) ")) + (matches ":coolguy PRIVMSG bar :lol :) ")) + ((desc . "Simple test with source and empty trailing.") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" "")) + (matches ":coolguy foo bar baz :")) + ((desc . "Trailing contains only spaces.") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" " ")) + (matches ":coolguy foo bar baz : ")) + ((desc . "Param containing tab (tab is not considered SPACE for message splitting).") + (atoms + (source . "coolguy") + (verb . "foo") + (params "b ar" "baz")) + (matches ":coolguy foo b ar baz" ":coolguy foo b ar :baz")) + ((desc . "Tag with no value and space-filled trailing.") + (atoms + (tags + (asd . "")) + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" " ")) + (matches "@asd :coolguy foo bar baz : ")) + ((desc . "Tags with escaped values.") + (atoms + (verb . "foo") + (tags + (a . "b\\and\nk") + (d . "gh;764"))) + (matches "@a=b\\\\and\\nk;d=gh\\:764 foo" "@d=gh\\:764;a=b\\\\and\\nk foo")) + ((desc . "Tags with escaped values and params.") + (atoms + (verb . "foo") + (tags + (a . "b\\and\nk") + (d . "gh;764")) + (params "par1" "par2")) + (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")) + ((desc . "Tag with long, strange values (including LF and newline).") + (atoms + (tags + (foo . "\\\\;\\s \n")) + (verb . "COMMAND")) + (matches "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND")))) + (msg-split + (tests + ((input . "foo bar baz asdf") + (atoms + (verb . "foo") + (params "bar" "baz" "asdf"))) + ((input . ":coolguy foo bar baz asdf") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" "asdf"))) + ((input . "foo bar baz :asdf quux") + (atoms + (verb . "foo") + (params "bar" "baz" "asdf quux"))) + ((input . "foo bar baz :") + (atoms + (verb . "foo") + (params "bar" "baz" ""))) + ((input . "foo bar baz ::asdf") + (atoms + (verb . "foo") + (params "bar" "baz" ":asdf"))) + ((input . ":coolguy foo bar baz :asdf quux") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" "asdf quux"))) + ((input . ":coolguy foo bar baz : asdf quux ") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" " asdf quux "))) + ((input . ":coolguy PRIVMSG bar :lol :) ") + (atoms + (source . "coolguy") + (verb . "PRIVMSG") + (params "bar" "lol :) "))) + ((input . ":coolguy foo bar baz :") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" ""))) + ((input . ":coolguy foo bar baz : ") + (atoms + (source . "coolguy") + (verb . "foo") + (params "bar" "baz" " "))) + ((input . "@a=b;c=32;k;rt=ql7 foo") + (atoms + (verb . "foo") + (tags + (a . "b") + (c . "32") + (k . "") + (rt . "ql7")))) + ((input . "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo") + (atoms + (verb . "foo") + (tags + (a . "b\\and\nk") + (c . "72 45") + (d . "gh;764")))) + ((input . "@c;h=;a=b :quux ab cd") + (atoms + (tags + (c . "") + (h . "") + (a . "b")) + (source . "quux") + (verb . "ab") + (params "cd"))) + ((input . ":src JOIN #chan") + (atoms + (source . "src") + (verb . "JOIN") + (params "#chan"))) + ((input . ":src JOIN :#chan") + (atoms + (source . "src") + (verb . "JOIN") + (params "#chan"))) + ((input . ":src AWAY") + (atoms + (source . "src") + (verb . "AWAY"))) + ((input . ":src AWAY ") + (atoms + (source . "src") + (verb . "AWAY"))) + ((input . ":cool guy foo bar baz") + (atoms + (source . "cool guy") + (verb . "foo") + (params "bar" "baz"))) + ((input . ":coolguy!ag@net5work.admin PRIVMSG foo :bar baz") + (atoms + (source . "coolguy!ag@net5work.admin") + (verb . "PRIVMSG") + (params "foo" "bar baz"))) + ((input . ":coolguy!~ag@net05work.admin PRIVMSG foo :bar baz") + (atoms + (source . "coolguy!~ag@net05work.admin") + (verb . "PRIVMSG") + (params "foo" "bar baz"))) + ((input . "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3") + (atoms + (tags + (tag1 . "value1") + (tag2 . "") + (vendor1/tag3 . "value2") + (vendor2/tag4 . "")) + (source . "irc.example.com") + (verb . "COMMAND") + (params "param1" "param2" "param3 param3"))) + ((input . ":irc.example.com COMMAND param1 param2 :param3 param3") + (atoms + (source . "irc.example.com") + (verb . "COMMAND") + (params "param1" "param2" "param3 param3"))) + ((input . "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 COMMAND param1 param2 :param3 param3") + (atoms + (tags + (tag1 . "value1") + (tag2 . "") + (vendor1/tag3 . "value2") + (vendor2/tag4 . "")) + (verb . "COMMAND") + (params "param1" "param2" "param3 param3"))) + ((input . "COMMAND") + (atoms + (verb . "COMMAND"))) + ((input . "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND") + (atoms + (tags + (foo . "\\\\;\\s \n")) + (verb . "COMMAND"))) + ((input . ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters") + (atoms + (source . "gravel.mozilla.org") + (verb . "432") + (params "#momo" "Erroneous Nickname: Illegal characters"))) + ((input . ":gravel.mozilla.org MODE #tckk +n ") + (atoms + (source . "gravel.mozilla.org") + (verb . "MODE") + (params "#tckk" "+n"))) + ((input . ":services.esper.net MODE #foo-bar +o foobar ") + (atoms + (source . "services.esper.net") + (verb . "MODE") + (params "#foo-bar" "+o" "foobar"))) + ((input . "@tag1=value\\\\ntest COMMAND") + (atoms + (tags + (tag1 . "value\\ntest")) + (verb . "COMMAND"))) + ((input . "@tag1=value\\1 COMMAND") + (atoms + (tags + (tag1 . "value1")) + (verb . "COMMAND"))) + ((input . "@tag1=value1\\ COMMAND") + (atoms + (tags + (tag1 . "value1")) + (verb . "COMMAND"))) + ((input . "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND") + (atoms + (tags + (tag1 . "5") + (tag2 . "3") + (tag3 . "4")) + (verb . "COMMAND"))) + ((input . "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND") + (atoms + (tags + (tag1 . "5") + (tag2 . "3") + (tag3 . "4") + (vendor/tag2 . "8")) + (verb . "COMMAND"))) + ((input . ":SomeOp MODE #channel :+i") + (atoms + (source . "SomeOp") + (verb . "MODE") + (params "#channel" "+i"))) + ((input . ":SomeOp MODE #channel +oo SomeUser :AnotherUser") + (atoms + (source . "SomeOp") + (verb . "MODE") + (params "#channel" "+oo" "SomeUser" "AnotherUser"))))) + (userhost-split + (tests + ((source . "coolguy") + (atoms + (nick . "coolguy"))) + ((source . "coolguy!ag@127.0.0.1") + (atoms + (nick . "coolguy") + (user . "ag") + (host . "127.0.0.1"))) + ((source . "coolguy!~ag@localhost") + (atoms + (nick . "coolguy") + (user . "~ag") + (host . "localhost"))) + ((source . "coolguy@127.0.0.1") + (atoms + (nick . "coolguy") + (host . "127.0.0.1"))) + ((source . "coolguy!ag") + (atoms + (nick . "coolguy") + (user . "ag"))) + ((source . "coolguy!ag@net5work.admin") + (atoms + (nick . "coolguy") + (user . "ag") + (host . "net5work.admin"))) + ((source . "coolguy!~ag@net05work.admin") + (atoms + (nick . "coolguy") + (user . "~ag") + (host . "net05work.admin"))))) + (validate-hostname + (tests + ((host . "irc.example.com") + (valid . t)) + ((host . "i.coolguy.net") + (valid . t)) + ((host . "irc-srv.net.uk") + (valid . t)) + ((host . "iRC.CooLguY.NeT") + (valid . t)) + ((host . "gsf.ds342.co.uk") + (valid . t)) + ((host . "324.net.uk") + (valid . t)) + ((host . "xn--bcher-kva.ch") + (valid . t)) + ((host . "-lol-.net.uk") + (valid . :false)) + ((host . "-lol.net.uk") + (valid . :false)) + ((host . "_irc._sctp.lol.net.uk") + (valid . :false)) + ((host . "irc") + (valid . :false)) + ((host . "com") + (valid . :false)) + ((host . "") + (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 @@ +;; -*- mode: lisp-data; -*- +((pass 1 "PASS " (? ?:) "a")) +((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 @@ +;; -*- mode: lisp-data; -*- +((pass 1 "PASS " (? ?:) "b")) +((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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester") + (0 ":irc.example.org 002 tester :Your host is irc.example.org") + (0 ":irc.example.org 003 tester :This server was created just now") + (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + ;; Just to mix thing's up (force handler to schedule timer) + (0.1 ":irc.example.org 252 tester 0 :IRC Operators online") + (0 ":irc.example.org 253 tester 0 :unregistered connections") + (0 ":irc.example.org 254 tester 1 :channels formed") + (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers") + (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3") + (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3") + (0 ":irc.example.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0 ":irc.example.org 221 tester +Zi") + (0 ":irc.example.org 306 tester :You have been marked as being away") + (0 ":tester!~tester@localhost JOIN #chan") + (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.example.org 366 alice #chan :End of NAMES list")) + +((mode-chan 1.2 "MODE #chan") + (0 ":bob!~bob@example.org PRIVMSG #chan :hey")) + +((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 @@ +;;; -*- mode: lisp-data -*- +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0.0 ":irc.org 001 tester :Welcome to the Internet Relay Network tester") + (0.0 ":irc.org 002 tester :Your host is irc.org") + (0.0 ":irc.org 003 tester :This server was created just now") + (0.0 ":irc.org 004 tester irc.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0.0 ":irc.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0.0 ":irc.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0.0 ":irc.org 252 tester 0 :IRC Operators online") + (0.0 ":irc.org 253 tester 0 :unregistered connections") + (0.0 ":irc.org 254 tester 1 :channels formed") + (0.0 ":irc.org 255 tester :I have 3 clients and 0 servers") + (0.0 ":irc.org 265 tester 3 3 :Current local users 3, max 3") + (0.0 ":irc.org 266 tester 3 3 :Current global users 3, max 3") + (0.0 ":irc.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0.0 ":irc.org 221 tester +Zi") + (0.0 ":irc.org 306 tester :You have been marked as being away")) + +((join-foo 1.2 "JOIN #foo") + (0 ":tester!~tester@localhost JOIN #foo") + (0 ":irc.example.org 353 alice = #foo :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.example.org 366 alice #foo :End of NAMES list")) + +;; This would time out if the mode-foo's outgoing blocked (remove minus signs to see) +((~join-bar 1.5 "JOIN #bar") + (0 ":tester!~tester@localhost JOIN #bar") + (0 ":irc.example.org 353 alice = #bar :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.example.org 366 alice #bar :End of NAMES list")) + +((mode-foo 1.2 "MODE #foo") + (0.0 ":irc.example.org 324 tester #foo +Cint") + (0.0 ":irc.example.org 329 tester #foo 1519850102") + (-0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defence, by mercy, 'tis most just.") + (-0.2 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: Grows, lives, and dies, in single blessedness.") + (-0.3 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: For these two hours, Rosalind, I will leave thee.") + (-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.") + (-0.5 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: That I must love a loathed enemy.") + (-0.6 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: As't please your lordship: I'll leave you.") + (-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.") + (-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.") + (-0.9 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: As living here and you no use of him.") + (-1.0 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: If there be truth in sight, you are my Rosalind.") + (-1.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: That is another's lawful promis'd love.") + (-1.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :I am heard.")) + +((mode-bar 1.5 "MODE #bar") + (0.0 ":irc.example.org 324 tester #bar +HMfnrt 50:5h :10:5") + (0.0 ":irc.example.org 329 tester #bar :1602642829") + (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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester") + (0 ":irc.example.org 002 tester :Your host is irc.example.org") + (0 ":irc.example.org 003 tester :This server was created just now") + (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0 ":irc.example.org 252 tester 0 :IRC Operators online") + (0 ":irc.example.org 253 tester 0 :unregistered connections") + (0 ":irc.example.org 254 tester 1 :channels formed") + (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers") + (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3") + (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3") + (0 ":irc.example.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0 ":irc.example.org 221 tester +Zi") + (0 ":irc.example.org 306 tester :You have been marked as being away")) + +((join 1.2 "JOIN #chan") + (0 ":tester!~tester@localhost JOIN #chan") + (0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org") + (0 ":irc.example.org 366 alice #chan :End of NAMES list")) + +((mode-chan 0.2 "MODE #chan") + (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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((~ping 1.2 "PING " nonce) + (0.1 ":irc.example.org PONG irc.example.com " echo)) + +((user 0.2 "USER user 0 * :tester") + (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester") + (0 ":irc.example.org 002 tester :Your host is irc.example.org") + (0 ":irc.example.org 003 tester :This server was created just now") + (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0 ":irc.example.org 252 tester 0 :IRC Operators online") + (0 ":irc.example.org 253 tester 0 :unregistered connections") + (0 ":irc.example.org 254 tester 1 :channels formed") + (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers") + (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3") + (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3") + (0 ":irc.example.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0 ":irc.example.org 221 tester +Zi") + (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 @@ +;;; -*- mode: lisp-data -*- +((one 1 "ONE one")) +((two 1 "TWO two")) +((blank 1 "")) +((one-space 1 " ")) +((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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) network ":changeme")) +((nick 1.2 "NICK tester")) + +((user 1.2 "USER user 0 * :tester") + (0.001 ":" fqdn " 001 tester :Welcome to the BAR Network tester") + (0.002 ":" fqdn " 002 tester :Your host is " fqdn) + (0.003 ":" fqdn " 003 tester :This server was created just now") + (0.004 ":" fqdn " 004 tester " fqdn " BERios CEIRabehiklmnoqstv Iabehkloqv") + (0.005 ":" fqdn " 005 tester MODES NETWORK=" net " NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0.006 ":" fqdn " 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0.007 ":" fqdn " 252 tester 0 :IRC Operators online") + (0.008 ":" fqdn " 253 tester 0 :unregistered connections") + (0.009 ":" fqdn " 254 tester 1 :channels formed") + (0.010 ":" fqdn " 255 tester :I have 3 clients and 0 servers") + (0.011 ":" fqdn " 265 tester 3 3 :Current local users 3, max 3") + (0.012 ":" fqdn " 266 tester 3 3 :Current global users 3, max 3") + (0.013 ":" fqdn " 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0.014 ":" fqdn " 221 tester +Zi") + (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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) network ":changeme")) +((nick 1.2 "NICK tester")) + +((user 2.2 "USER user 0 * :tester") + (0.015 ":" fqdn " 001 tester :Welcome to the FOO Network tester") + (0.014 ":" fqdn " 002 tester :Your host is " fqdn) + (0.013 ":" fqdn " 003 tester :This server was created just now") + (0.012 ":" fqdn " 004 tester " fqdn " BERios CEIRabehiklmnoqstv Iabehkloqv") + (0.011 ":" fqdn " 005 tester MODES NETWORK=" net " NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0.010 ":" fqdn " 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0.009 ":" fqdn " 252 tester 0 :IRC Operators online") + (0.008 ":" fqdn " 253 tester 0 :unregistered connections") + (0.007 ":" fqdn " 254 tester 1 :channels formed") + (0.006 ":" fqdn " 255 tester :I have 3 clients and 0 servers") + (0.005 ":" fqdn " 265 tester 3 3 :Current local users 3, max 3") + (0.004 ":" fqdn " 266 tester 3 3 :Current global users 3, max 3") + (0.003 ":" fqdn " 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0.002 ":" fqdn " 221 tester +Zi") + (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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :" (group (+ alpha)) eos) + (0 ":*status!znc@znc.in NOTICE " nick " :You have no networks configured." + " Use /znc AddNetwork to add one.") + (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 @@ +;;; proxy-subprocess.el --- Example setup file for erc-d -*- lexical-binding: t; -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see +;; . + +;;; Commentary: +;;; Code: + +(defvar erc-d-tmpl-vars) + +(setq erc-d-tmpl-vars + + (list + (cons 'fqdn (lambda (helper) + (let ((name (funcall helper :dialog-name))) + (funcall helper :set + (if (eq name 'proxy-foonet) + "irc.foo.net" + "irc.bar.net"))))) + + (cons 'net (lambda (helper) + (let ((name (funcall helper :dialog-name))) + (funcall helper :set + (if (eq name 'proxy-foonet) + "FooNet" + "BarNet"))))) + + (cons 'network '(group (+ alpha))))) + +;;; 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 @@ +;;; -*- mode: lisp-data -*- + +((pass 10.0 "PASS " (? ?:) "changeme")) +((nick 0.2 "NICK tester")) + +((user 0.2 "USER user 0 * :tester") + (0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester") + (0 ":irc.example.org 002 tester :Your host is irc.example.org") + (0 ":irc.example.org 003 tester :This server was created just now") + (0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0 ":irc.example.org 252 tester 0 :IRC Operators online") + (0 ":irc.example.org 253 tester 0 :unregistered connections") + (0 ":irc.example.org 254 tester 1 :channels formed") + (0 ":irc.example.org 255 tester :I have 3 clients and 0 servers") + (0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3") + (0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3") + (0 ":irc.example.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0 ":irc.example.org 221 tester +Zi") + (0 ":irc.example.org 306 tester :You have been marked as being away")) + +((mode 0.2 "MODE #chan") + (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 @@ +;;; -*- mode: lisp-data -*- +((t 10.0 "PASS " (? ?:) "changeme")) +((t 0.2 "NICK tester")) + +((t 0.2 "USER user 0 * :tester") + (0.0 ":irc.example.org 001 tester :Welcome to the Internet Relay Network tester") + (0.0 ":irc.example.org 002 tester :Your host is irc.example.org") + (0.0 ":irc.example.org 003 tester :This server was created just now") + (0.0 ":irc.example.org 004 tester irc.example.org BERios CEIRabehiklmnoqstv Iabehkloqv") + (0.0 ":irc.example.org 005 tester MODES NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+" + " :are supported by this server") + (0.0 ":irc.example.org 251 tester :There are 3 users and 0 invisible on 1 server(s)") + (0.0 ":irc.example.org 252 tester 0 :IRC Operators online") + (0.0 ":irc.example.org 253 tester 0 :unregistered connections") + (0.0 ":irc.example.org 254 tester 1 :channels formed") + (0.0 ":irc.example.org 255 tester :I have 3 clients and 0 servers") + (0.0 ":irc.example.org 265 tester 3 3 :Current local users 3, max 3") + (0.0 ":irc.example.org 266 tester 3 3 :Current global users 3, max 3") + (0.0 ":irc.example.org 422 tester :MOTD File is missing")) + +((mode-user 1.2 "MODE tester +i") + (0.0 ":irc.example.org 221 tester +Zi") + + (0.0 ":irc.example.org 306 tester :You have been marked as being away") + (0.0 ":tester!~tester@localhost JOIN #chan") + (0.0 ":irc.example.org 353 alice = #chan :+alice!~alice@example.com @%+bob!~bob@example.org") + (0.0 ":irc.example.org 366 alice #chan :End of NAMES list") + (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey")) -- cgit v1.2.3 From 0a0ec8958a3026d04101a0501d117a0195df8097 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 1 Jul 2022 05:47:31 -0700 Subject: ; Fix regexp in ERC test-server utility * test/lisp/erc/resources/erc-d/erc-d-i.el (erc-d-i--tag-unescaped-regexp): Thanks to Mattias and relint for catching this. --- test/lisp/erc/resources/erc-d/erc-d-i.el | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'test/lisp/erc/resources/erc-d') diff --git a/test/lisp/erc/resources/erc-d/erc-d-i.el b/test/lisp/erc/resources/erc-d/erc-d-i.el index 27b1bf60839..db113335a82 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-i.el +++ b/test/lisp/erc/resources/erc-d/erc-d-i.el @@ -39,14 +39,14 @@ UTF-8 text before parsing, which is nonstandard." (defconst erc-d-i--tag-escapes '((";" . "\\:") (" " . "\\s") ("\\" . "\\\\") ("\r" . "\\r") ("\n" . "\\n"))) -;; XXX these are not mirror inverses; unescaping may degenerate -;; original by dropping stranded/misplaced backslashes. +;; These are not mirror inverses; unescaping may drop stranded or +;; misplaced backslashes. (defconst erc-d-i--tag-escaped-regexp (rx (or ?\; ?\ ?\\ ?\r ?\n))) (defconst erc-d-i--tag-unescaped-regexp (rx (or "\\:" "\\s" "\\\\" "\\r" "\\n" - (seq "\\" (or string-end (not (or ":" "n" "r" "\\"))))))) + (seq "\\" (or string-end (not (or ":" "s" "n" "r" "\\"))))))) (defun erc-d-i--unescape-tag-value (str) "Undo substitution of char placeholders in raw tag value STR." @@ -65,8 +65,6 @@ UTF-8 text before parsing, which is nonstandard." (defconst erc-d-i--invalid-tag-regexp (rx (any "\0\7\r\n; "))) -;; This is `erc-v3-message-tags' with fatal errors. - (defun erc-d-i--validate-tags (raw) "Validate tags portion of some RAW incoming message. RAW must not have a leading \"@\" or a trailing space. The spec says -- cgit v1.2.3 From 2a05479c221d4a13b15ed731e4eb1c0de99e97ed Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Thu, 14 Jul 2022 11:55:52 +0200 Subject: ; Fix typos: prefer American spelling --- doc/lispref/commands.texi | 2 +- doc/lispref/modes.texi | 2 +- doc/misc/eshell.texi | 2 +- doc/misc/modus-themes.org | 8 ++++---- etc/NEWS | 6 +++--- etc/images/README | 2 +- etc/themes/leuven-dark-theme.el | 2 +- lisp/dnd.el | 8 ++++---- lisp/emacs-lisp/byte-opt.el | 2 +- lisp/emacs-lisp/bytecomp.el | 2 +- lisp/emacs-lisp/macroexp.el | 2 +- lisp/erc/erc-backend.el | 2 +- lisp/files.el | 2 +- lisp/icomplete.el | 8 ++++---- lisp/jsonrpc.el | 12 ++++++------ lisp/net/eudc-capf.el | 2 +- lisp/net/eudc.el | 2 +- lisp/org/org-plot.el | 6 +++--- lisp/textmodes/texinfo.el | 2 +- src/haiku_support.cc | 2 +- src/keyboard.c | 2 +- src/lread.c | 2 +- src/nsselect.m | 2 +- .../lisp/erc/resources/base/assoc/bouncer-history/barnet.eld | 2 +- test/lisp/erc/resources/base/assoc/reconplay/foonet.eld | 2 +- test/lisp/erc/resources/base/netid/bouncer/barnet-again.eld | 2 +- test/lisp/erc/resources/base/netid/bouncer/foonet-drop.eld | 2 +- test/lisp/erc/resources/base/netid/bouncer/foonet.eld | 2 +- .../erc/resources/base/renick/queries/bouncer-barnet.eld | 2 +- .../lisp/erc/resources/base/reuse-buffers/channel/barnet.eld | 4 ++-- .../erc/resources/base/upstream-reconnect/soju-barnet.eld | 2 +- test/lisp/erc/resources/erc-d/resources/incremental.eld | 2 +- test/lisp/erc/resources/erc-d/resources/no-block.eld | 2 +- test/src/keymap-tests.el | 8 ++++---- 34 files changed, 56 insertions(+), 56 deletions(-) (limited to 'test/lisp/erc/resources/erc-d') diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 1718978a395..865fa26b275 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2977,7 +2977,7 @@ returns the key sequence as a vector, never as a string. If an input character is upper-case (or has the shift modifier) and has no key binding, but its lower-case equivalent has one, then @code{read-key-sequence} converts the character to lower case. (This -behaviour can be disabled by setting the +behavior can be disabled by setting the @code{translate-upper-case-key-bindings} user option to @code{nil}.) Note that @code{lookup-key} does not perform case conversion in this way. diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index 2ba37e413c0..e94093318fc 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi @@ -271,7 +271,7 @@ normal-mode}), but tries to force it not to choose any modes in @defun clean-mode Changing the major mode clears out most local variables, but it -doesn't remove all artefacts in the buffer (like text properties and +doesn't remove all artifacts in the buffer (like text properties and overlays). It's rare to change a buffer from one major mode to another (except from @code{fundamental-mode} to everything else), so this is usually not a concern. It can sometimes be convenient (mostly diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index f6ec1e268a0..963657f102a 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1597,7 +1597,7 @@ integration: using the remote shell's pipelining avoids copying the data which will flow through the pipeline to local Emacs buffers and then right back again. -Eshell recognises a special syntax to make it easier to convert +Eshell recognizes a special syntax to make it easier to convert pipelines so as to bypass Eshell's pipelining. Prefixing at least one @code{|}, @code{<} or @code{>} with an asterisk marks a command as intended for the operating system shell. To make it harder to invoke diff --git a/doc/misc/modus-themes.org b/doc/misc/modus-themes.org index 4770e3a5191..d0d985705f1 100644 --- a/doc/misc/modus-themes.org +++ b/doc/misc/modus-themes.org @@ -4542,7 +4542,7 @@ The =git-gutter= and =git-gutter-fr= packages default to drawing bitmaps for the indicators they display (e.g. bitmap of a plus sign for added lines). In Doom Emacs, these bitmaps are replaced with contiguous lines which may look nicer, but require a change to the foreground of the -relevant faces to yield the desired colour combinations. +relevant faces to yield the desired color combinations. Since this is Doom-specific, we urge users to apply changes in their local setup. Below is some sample code, based on what we cover at @@ -5519,7 +5519,7 @@ interface virtually unusable. [[#h:5808be52-361a-4d18-88fd-90129d206f9b][Option for links]]. -Again, one must exercise judgement in order to avoid discrimination, +Again, one must exercise judgment in order to avoid discrimination, where "discrimination" refers to: + The treatment of substantially different magnitudes as if they were of @@ -5535,11 +5535,11 @@ usability beyond matters of color---they would be making a not-so-obvious error of treating different cases as if they were the same. -The Modus themes prioritise "thematic consistency" over abstract harmony +The Modus themes prioritize "thematic consistency" over abstract harmony or regularity among their applicable colors. In concrete terms, we do not claim that, say, our yellows are the best complements for our blues because we generally avoid using complementary colors side-by-side, so -it is wrong to optimise for a decontextualised blue+yellow combination. +it is wrong to optimize for a decontextualised blue+yellow combination. Not to imply that our colors do not work well together because they do, just to clarify that consistency of context is what themes must strive for, and that requires widening the scope of the design beyond the diff --git a/etc/NEWS b/etc/NEWS index 19f6879a8c7..57845df9792 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -876,7 +876,7 @@ are met. The conditions are given by the argument, which can be +++ *** New user option 'rcirc-cycle-completion-flag'. Rcirc will use the default 'completion-at-point' mechanism. The -conventional IRC behaviour of completing by cycling through the +conventional IRC behavior of completing by cycling through the available options can be restored by enabling this option. ** Imenu @@ -1221,7 +1221,7 @@ longer available after exiting the recursive edit. This user option controls whether the 'e' (in a "*Backtrace*" buffer or while edebugging) and 'C-x C-e' (while edebugging) commands lead to a (further) backtrace. By default, this variable is nil, -which is a change in behaviour from previous Emacs versions. +which is a change in behavior from previous Emacs versions. +++ *** 'e' in edebug can now take a prefix arg to pretty-print the results. @@ -1452,7 +1452,7 @@ header before sending a message. ** Texinfo Mode --- -*** 'texinfo-mode' now has a specialised 'narrow-to-defun' definition. +*** 'texinfo-mode' now has a specialized 'narrow-to-defun' definition. It narrows to the current node. ** EUDC diff --git a/etc/images/README b/etc/images/README index 72da92427b4..858f33e40ba 100644 --- a/etc/images/README +++ b/etc/images/README @@ -112,7 +112,7 @@ GNOME project). They are not part of Emacs, but are distributed and used by Emacs. They are licensed under either the GNU LGPL v3 or the Creative Commons Attribution-Share Alike 3.0 United States License. -To view a copy of the CC-BY-SA licence, visit +To view a copy of the CC-BY-SA license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. diff --git a/etc/themes/leuven-dark-theme.el b/etc/themes/leuven-dark-theme.el index 3fbb9d6c995..0e162c8bab9 100644 --- a/etc/themes/leuven-dark-theme.el +++ b/etc/themes/leuven-dark-theme.el @@ -792,7 +792,7 @@ more...") `(org-example ((,class (:foreground "#ffff0b" :background "#38203d")))) `(org-footnote ((,class (:underline t :foreground "#ff7138")))) `(org-formula ((,class (:foreground "#0680e1")))) - ;; org-habit colours are thanks to zenburn + ;; org-habit colors are thanks to zenburn `(org-habit-ready-face ((t :background "#7F9F7F"))) ; ,zenburn-green `(org-habit-alert-face ((t :background "#E0CF9F" :foreground "#3F3F3F"))) ; ,zenburn-yellow-1 fg ,zenburn-bg `(org-habit-clear-face ((t :background "#5C888B"))) ; ,zenburn-blue-3 diff --git a/lisp/dnd.el b/lisp/dnd.el index ade61917e96..70852885a86 100644 --- a/lisp/dnd.el +++ b/lisp/dnd.el @@ -327,7 +327,7 @@ in that list instead." "Begin dragging TEXT from FRAME. Initate a drag-and-drop operation allowing the user to drag text from Emacs to another program (the drop target), then block until -the drop is completed or is cancelled. +the drop is completed or is canceled. If the drop completed, return the action that the drop target actually performed, which can be one of the following symbols: @@ -341,7 +341,7 @@ actually performed, which can be one of the following symbols: - `private', which means the drop target chose to perform an unspecified action. -Return nil if the drop was cancelled. +Return nil if the drop was canceled. TEXT is a string containing text that will be inserted by the program where the drop happened. FRAME is the frame where the @@ -383,7 +383,7 @@ currently being held down. It should only be called upon a "Begin dragging FILE from FRAME. Initate a drag-and-drop operation allowing the user to drag a file from Emacs to another program (the drop target), then block until -the drop happens or is cancelled. +the drop happens or is canceled. Return the action that the drop target actually performed, which can be one of the following symbols: @@ -399,7 +399,7 @@ can be one of the following symbols: - `private', which means the drop target chose to perform an unspecified action. -Return nil if the drop was cancelled. +Return nil if the drop was canceled. FILE is the file name that will be sent to the program where the drop happened. If it is a remote file, Emacs will make a diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index d8a96b3f020..a24a5044562 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -425,7 +425,7 @@ for speeding up processing.") ;; `unwind-protect' is a special form which here takes the shape ;; (unwind-protect EXPR :fun-body UNWIND-FUN). ;; We can treat it as if it were a plain function at this point, - ;; although there are specific optimisations possible. + ;; although there are specific optimizations possible. ;; In particular, the return value of UNWIND-FUN is never used ;; so its body should really be compiled for-effect, but we ;; don't do that right now. diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index 8df4133b6b0..86681cf4dd4 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -4229,7 +4229,7 @@ This function is never called when `lexical-binding' is nil." (byte-defop-compiler-1 quote) (defun byte-compile-setq (form) - (cl-assert (= (length form) 3)) ; normalised in macroexp + (cl-assert (= (length form) 3)) ; normalized in macroexp (let ((var (nth 1 form)) (expr (nth 2 form))) (byte-compile-form expr) diff --git a/lisp/emacs-lisp/macroexp.el b/lisp/emacs-lisp/macroexp.el index 4db50bbaa9b..6a193a56d2d 100644 --- a/lisp/emacs-lisp/macroexp.el +++ b/lisp/emacs-lisp/macroexp.el @@ -378,7 +378,7 @@ Assumes the caller has bound `macroexpand-all-environment'." form `(,fn ,var ,new-expr)))) (`(setq . ,args) - ;; Normalise to a sequence of (setq SYM EXPR). + ;; Normalize to a sequence of (setq SYM EXPR). ;; Malformed code is translated to code that signals an error ;; at run time. (let ((nargs (length args))) diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index bc7a7d14dc2..8be4894ecbb 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -1644,7 +1644,7 @@ Then display the welcome message." "Return list of unescaped components from an \"ISUPPORT\" VALUE." ;; https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03#section-2 ;; - ;; > The server SHOULD send "X", not "X="; this is the normalised form. + ;; > The server SHOULD send "X", not "X="; this is the normalized form. ;; ;; Note: for now, assume the server will only send non-empty values, ;; possibly with printable ASCII escapes. Though in practice, the diff --git a/lisp/files.el b/lisp/files.el index bdceaefb0ff..25b58423649 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -8000,7 +8000,7 @@ If RESTART, restart Emacs after killing the current Emacs process." ("Close Without Saving" . no-save) ("Save All" . save-all) ("Cancel" . cancel))) - ('cancel (user-error "Exit cancelled")) + ('cancel (user-error "Exit canceled")) ('save-all (save-some-buffers t))) (save-some-buffers arg t))) (let ((confirm confirm-kill-emacs)) diff --git a/lisp/icomplete.el b/lisp/icomplete.el index 381ad5466f5..b1fcf9ae712 100644 --- a/lisp/icomplete.el +++ b/lisp/icomplete.el @@ -840,13 +840,13 @@ by `group-function''s second \"transformation\" protocol." while (listp r) count 1)) repeat total-space - for neighbour = nil + for neighbor = nil if (and preds (> space-above 0)) do - (push (setq neighbour (pop preds)) scroll-above) + (push (setq neighbor (pop preds)) scroll-above) (cl-decf space-above) else if (consp succs) collect - (setq neighbour (pop succs)) into scroll-below-aux - while neighbour + (setq neighbor (pop succs)) into scroll-below-aux + while neighbor finally (setq scroll-below scroll-below-aux)) ;; Halfway there... (let* ((selected (propertize (car comps) 'icomplete-selected t)) diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el index b84e9b74b1f..90833e1c1d7 100644 --- a/lisp/jsonrpc.el +++ b/lisp/jsonrpc.el @@ -277,7 +277,7 @@ the function is waiting, then it exits immediately, returning CANCEL-ON-INPUT-RETVAL. Any future replies (normal or error) are ignored." (let* ((tag (cl-gensym "jsonrpc-request-catch-tag")) id-and-timer - cancelled + canceled (retval (unwind-protect (catch tag @@ -287,26 +287,26 @@ ignored." #'jsonrpc--async-request-1 connection method params :success-fn (lambda (result) - (unless cancelled + (unless canceled (throw tag `(done ,result)))) :error-fn (jsonrpc-lambda (&key code message data) - (unless cancelled + (unless canceled (throw tag `(error (jsonrpc-error-code . ,code) (jsonrpc-error-message . ,message) (jsonrpc-error-data . ,data))))) :timeout-fn (lambda () - (unless cancelled + (unless canceled (throw tag '(error (jsonrpc-error-message . "Timed out"))))) `(,@(when deferred `(:deferred ,deferred)) ,@(when timeout `(:timeout ,timeout))))) (cond (cancel-on-input (unwind-protect (let ((inhibit-quit t)) (while (sit-for 30))) - (setq cancelled t)) - `(cancelled ,cancel-on-input-retval)) + (setq canceled t)) + `(canceled ,cancel-on-input-retval)) (t (while t (accept-process-output nil 30))))) ;; In normal operation, cancellation is handled by the ;; timeout function and response filter, but we still have diff --git a/lisp/net/eudc-capf.el b/lisp/net/eudc-capf.el index 68cbfd93ffe..92f0c80493d 100644 --- a/lisp/net/eudc-capf.el +++ b/lisp/net/eudc-capf.el @@ -71,7 +71,7 @@ ;; setting. ;; ;; The value of the variable `eudc-capf-modes' indicates which -;; major modes do such a setup as part of their initialisation +;; major modes do such a setup as part of their initialization ;; code. ;;; Code: diff --git a/lisp/net/eudc.el b/lisp/net/eudc.el index 9208e40a730..5cfd4e25ec0 100644 --- a/lisp/net/eudc.el +++ b/lisp/net/eudc.el @@ -926,7 +926,7 @@ non-nil, collect results from all servers." `eudc-inline-expansion-format' is expected to return a list.") nil)))) - ;; fallback behaviour (nil function, or non-matching type) + ;; fallback behavior (nil function, or non-matching type) (t (let ((fname (cdr (assq (nth 0 query-attrs) res))) (lname (cdr (assq (nth 1 query-attrs) res))) diff --git a/lisp/org/org-plot.el b/lisp/org/org-plot.el index 4507fbe7ddc..7cce678a81b 100644 --- a/lisp/org/org-plot.el +++ b/lisp/org/org-plot.el @@ -272,10 +272,10 @@ argument for the FUNCTION." for k in keys collect (cons k (funcall function (lookup k alist1) (lookup k alist2)))))) -(defun org--plot/item-frequencies (values &optional normalise) +(defun org--plot/item-frequencies (values &optional normalize) "Return an alist indicating the frequency of values in VALUES list. -When NORMALISE is non-nil, the count is divided by the number of values." - (let ((normaliser (if normalise (float (length values)) 1))) +When NORMALIZE is non-nil, the count is divided by the number of values." + (let ((normaliser (if normalize (float (length values)) 1))) (cl-loop for (n . m) in (seq-group-by #'identity values) collect (cons n (/ (length m) normaliser))))) diff --git a/lisp/textmodes/texinfo.el b/lisp/textmodes/texinfo.el index 5d6f5deae1b..1ac59ddc5fb 100644 --- a/lisp/textmodes/texinfo.el +++ b/lisp/textmodes/texinfo.el @@ -401,7 +401,7 @@ REPORT-FN is the callback function." source beg end type msg) into diags finally (funcall report-fn diags))) - (flymake-log :warning "Cancelling obsolete check %s" + (flymake-log :warning "Canceling obsolete check %s" proc)) (kill-buffer (process-buffer proc))))))) (process-send-region texinfo--flymake-proc (point-min) (point-max)) diff --git a/src/haiku_support.cc b/src/haiku_support.cc index a3d3b7a17d3..1f7f372a9b4 100644 --- a/src/haiku_support.cc +++ b/src/haiku_support.cc @@ -142,7 +142,7 @@ enum struct font_selection_dialog_message { - /* Whether or not font selection was cancelled. */ + /* Whether or not font selection was canceled. */ bool_bf cancel : 1; /* Whether or not a size was explicitly specified. */ diff --git a/src/keyboard.c b/src/keyboard.c index c729d5dfb3e..2863058d633 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -12982,7 +12982,7 @@ Emacs allows binding both upper and lower case key sequences to commands. However, if there is a lower case key sequence bound to a command, and the user enters an upper case key sequence that is not bound to a command, Emacs will use the lower case binding. Setting -this variable to nil inhibits this behaviour. */); +this variable to nil inhibits this behavior. */); translate_upper_case_key_bindings = true; DEFVAR_BOOL ("input-pending-p-filter-events", diff --git a/src/lread.c b/src/lread.c index 759cc08946d..0b46a2e4ee5 100644 --- a/src/lread.c +++ b/src/lread.c @@ -4276,7 +4276,7 @@ read0 (Lisp_Object readcharfun, bool locate_syms) /* Catch silly games like #1=#1# */ invalid_syntax ("nonsensical self-reference", readcharfun); - /* Optimisation: since the placeholder is already + /* Optimization: since the placeholder is already a cons, repurpose it as the actual value. This allows us to skip the substitution below, since the placeholder is already referenced diff --git a/src/nsselect.m b/src/nsselect.m index c46bfeaf42a..5b47d746122 100644 --- a/src/nsselect.m +++ b/src/nsselect.m @@ -724,7 +724,7 @@ Return the action that the drop target actually chose to perform, or nil if no action was performed (either because there was no drop target, or the drop was rejected). If RETURN-FRAME is the symbol `now', also return any frame that mouse moves into during the -drag-and-drop operation, whilst simultaneously cancelling it. Any +drag-and-drop operation, whilst simultaneously canceling it. Any other non-nil value means to do the same, but to wait for the mouse to leave FRAME first. diff --git a/test/lisp/erc/resources/base/assoc/bouncer-history/barnet.eld b/test/lisp/erc/resources/base/assoc/bouncer-history/barnet.eld index 4b6ccfff38a..35a9a570b6d 100644 --- a/test/lisp/erc/resources/base/assoc/bouncer-history/barnet.eld +++ b/test/lisp/erc/resources/base/assoc/bouncer-history/barnet.eld @@ -37,7 +37,7 @@ ((mode 6 "MODE #chan") (0 ":irc.barnet.org 324 tester #chan +nt") (0 ":irc.barnet.org 329 tester #chan 1619593200") - (0.25 ":joe!~u@svpn88yjcdj42.irc PRIVMSG #chan :mike: But, in defence, by mercy, 'tis most just.") + (0.25 ":joe!~u@svpn88yjcdj42.irc PRIVMSG #chan :mike: But, in defense, by mercy, 'tis most just.") (0.25 ":mike!~u@xrir8fpe4d7ak.irc PRIVMSG #chan :joe: The Marshal of France, Monsieur la Far.") (0.25 ":joe!~u@xrir8fpe4d7ak.irc PRIVMSG #chan :mike: And bide the penance of each three years' day.") (0.25 ":mike!~u@xrir8fpe4d7ak.irc PRIVMSG #chan :joe: Madam, within; but never man so chang'd.") diff --git a/test/lisp/erc/resources/base/assoc/reconplay/foonet.eld b/test/lisp/erc/resources/base/assoc/reconplay/foonet.eld index 6f50ecca4ef..f916fea2374 100644 --- a/test/lisp/erc/resources/base/assoc/reconplay/foonet.eld +++ b/test/lisp/erc/resources/base/assoc/reconplay/foonet.eld @@ -28,7 +28,7 @@ (0.0 ":bob!~u@mw6kegwt77kwe.irc PRIVMSG #chan :[10:35:55] alice: This is but a custom in your tongue; you bear a graver purpose, I hope.") (0.0 ":alice!~u@mw6kegwt77kwe.irc PRIVMSG #chan :[10:37:16] bob: To imitate them; faults that are rich are fair.") (0.0 ":bob!~u@mw6kegwt77kwe.irc PRIVMSG #chan :[10:37:18] alice: Our Romeo hath not been in bed to-night.") - (0.0 ":alice!~u@mw6kegwt77kwe.irc PRIVMSG #chan :[10:37:21] bob: But, in defence, by mercy, 'tis most just.") + (0.0 ":alice!~u@mw6kegwt77kwe.irc PRIVMSG #chan :[10:37:21] bob: But, in defense, by mercy, 'tis most just.") (0.0 ":bob!~u@mw6kegwt77kwe.irc PRIVMSG #chan :[10:37:25] alice: Younger than she are happy mothers made.") (0.0 ":***!znc@znc.in PRIVMSG #chan :Playback Complete.") (0.0 ":irc.foonet.org 305 tester :You are no longer marked as being away")) diff --git a/test/lisp/erc/resources/base/netid/bouncer/barnet-again.eld b/test/lisp/erc/resources/base/netid/bouncer/barnet-again.eld index 766035a524c..e2fe1430283 100644 --- a/test/lisp/erc/resources/base/netid/bouncer/barnet-again.eld +++ b/test/lisp/erc/resources/base/netid/bouncer/barnet-again.eld @@ -41,7 +41,7 @@ ((mode 5 "MODE #chan") (0 ":irc.barnet.org 324 tester #chan +nt") (0 ":irc.barnet.org 329 tester #chan 1620805269") - (0.1 ":joe!~u@svpn88yjcdj42.irc PRIVMSG #chan :mike: But, in defence, by mercy, 'tis most just.") + (0.1 ":joe!~u@svpn88yjcdj42.irc PRIVMSG #chan :mike: But, in defense, by mercy, 'tis most just.") (0.1 ":mike!~u@xrir8fpe4d7ak.irc PRIVMSG #chan :joe: The Marshal of France, Monsieur la Far.") (0.1 ":joe!~u@xrir8fpe4d7ak.irc PRIVMSG #chan :mike: And bide the penance of each three years' day.") (0.1 ":mike!~u@xrir8fpe4d7ak.irc PRIVMSG #chan :joe: Madam, within; but never man so chang'd.") diff --git a/test/lisp/erc/resources/base/netid/bouncer/foonet-drop.eld b/test/lisp/erc/resources/base/netid/bouncer/foonet-drop.eld index e3c41e2133a..b99621cc311 100644 --- a/test/lisp/erc/resources/base/netid/bouncer/foonet-drop.eld +++ b/test/lisp/erc/resources/base/netid/bouncer/foonet-drop.eld @@ -36,7 +36,7 @@ (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: More evident than this; for this was stol'n.") (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Sell when you can; you are not for all markets.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: There's the fool hangs on your back already.") - (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Why, if you have a stomach to't, monsieur, if you think your mystery in stratagem can bring this instrument of honour again into its native quarter, be magnanimous in the enterprise and go on; I will grace the attempt for a worthy exploit: if you speed well in it, the duke shall both speak of it, and extend to you what further becomes his greatness, even to the utmost syllable of your worthiness.") + (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Why, if you have a stomach to't, monsieur, if you think your mystery in stratagem can bring this instrument of honor again into its native quarter, be magnanimous in the enterprise and go on; I will grace the attempt for a worthy exploit: if you speed well in it, the duke shall both speak of it, and extend to you what further becomes his greatness, even to the utmost syllable of your worthiness.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: For he hath still been tried a holy man.") (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: To have the touches dearest priz'd.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: And must advise the emperor for his good.") diff --git a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld index c241c59bb88..b0964fb9537 100644 --- a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld +++ b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld @@ -36,7 +36,7 @@ (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: More evident than this; for this was stol'n.") (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Sell when you can; you are not for all markets.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: There's the fool hangs on your back already.") - (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Why, if you have a stomach to't, monsieur, if you think your mystery in stratagem can bring this instrument of honour again into its native quarter, be magnanimous in the enterprise and go on; I will grace the attempt for a worthy exploit: if you speed well in it, the duke shall both speak of it, and extend to you what further becomes his greatness, even to the utmost syllable of your worthiness.") + (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: Why, if you have a stomach to't, monsieur, if you think your mystery in stratagem can bring this instrument of honor again into its native quarter, be magnanimous in the enterprise and go on; I will grace the attempt for a worthy exploit: if you speed well in it, the duke shall both speak of it, and extend to you what further becomes his greatness, even to the utmost syllable of your worthiness.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: For he hath still been tried a holy man.") (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: To have the touches dearest priz'd.") (0.1 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :alice: And must advise the emperor for his good.") diff --git a/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld b/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld index fc6cdaafe91..0c8cdac0379 100644 --- a/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld +++ b/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld @@ -35,7 +35,7 @@ ((mode 5 "MODE #chan") (0 ":irc.barnet.org 324 tester #chan +nt") (0 ":irc.barnet.org 329 tester #chan 1622538742") - (0.1 ":joe!~u@286u8jcpis84e.irc PRIVMSG #chan :mike: By favours several which they did bestow.") + (0.1 ":joe!~u@286u8jcpis84e.irc PRIVMSG #chan :mike: By favors several which they did bestow.") (0.1 ":mike!~u@286u8jcpis84e.irc PRIVMSG #chan :joe: You, Roderigo! come, sir, I am for you.")) ((privmsg-a 5 "PRIVMSG rando :Linda said you were gonna kill me.") diff --git a/test/lisp/erc/resources/base/reuse-buffers/channel/barnet.eld b/test/lisp/erc/resources/base/reuse-buffers/channel/barnet.eld index 82700c5912c..efc2506fd6f 100644 --- a/test/lisp/erc/resources/base/reuse-buffers/channel/barnet.eld +++ b/test/lisp/erc/resources/base/reuse-buffers/channel/barnet.eld @@ -27,7 +27,7 @@ (0 ":mike!~u@wvys46tx8tpmk.irc PRIVMSG #chan :[09:09:16] joe: Tush! none but minstrels like of sonneting.") (0 ":joe!~u@wvys46tx8tpmk.irc PRIVMSG #chan :[09:09:19] mike: Prithee, nuncle, be contented; 'tis a naughty night to swim in. Now a little fire in a wide field were like an old lecher's heart; a small spark, all the rest on's body cold. Look! here comes a walking fire.") (0 ":mike!~u@wvys46tx8tpmk.irc PRIVMSG #chan :[09:09:22] joe: My name is Edgar, and thy father's son.") - (0 ":joe!~u@wvys46tx8tpmk.irc PRIVMSG #chan :[09:09:26] mike: Good my lord, be good to me; your honour is accounted a merciful man; good my lord.") + (0 ":joe!~u@wvys46tx8tpmk.irc PRIVMSG #chan :[09:09:26] mike: Good my lord, be good to me; your honor is accounted a merciful man; good my lord.") (0 ":mike!~u@wvys46tx8tpmk.irc PRIVMSG #chan :[09:09:31] joe: Thy child shall live, and I will see it nourish'd.") (0 ":joe!~u@wvys46tx8tpmk.irc PRIVMSG #chan :[09:09:33] mike: Quick, quick; fear nothing; I'll be at thy elbow.") (0 ":***!znc@znc.in PRIVMSG #chan :Playback Complete.") @@ -38,7 +38,7 @@ (0 ":irc.barnet.org 324 tester #chan +nt") (0 ":irc.barnet.org 329 tester #chan 1620205534") (0.1 ":mike!~u@wvys46tx8tpmk.irc PRIVMSG #chan :joe: That will be given to the loudest noise we make.") - (0.1 ":joe!~u@wvys46tx8tpmk.irc PRIVMSG #chan :mike: If it please your honour, I am the poor duke's constable, and my name is Elbow: I do lean upon justice, sir; and do bring in here before your good honour two notorious benefactors.") + (0.1 ":joe!~u@wvys46tx8tpmk.irc PRIVMSG #chan :mike: If it please your honor, I am the poor duke's constable, and my name is Elbow: I do lean upon justice, sir; and do bring in here before your good honor two notorious benefactors.") (0.1 ":mike!~u@wvys46tx8tpmk.irc PRIVMSG #chan :joe: Following the signs, woo'd but the sign of she.") (0.5 ":joe!~u@wvys46tx8tpmk.irc PRIVMSG #chan :mike: That, sir, which I will not report after her.") (0.1 ":mike!~u@wvys46tx8tpmk.irc PRIVMSG #chan :joe: Boyet, prepare: I will away to-night.") diff --git a/test/lisp/erc/resources/base/upstream-reconnect/soju-barnet.eld b/test/lisp/erc/resources/base/upstream-reconnect/soju-barnet.eld index b8fc45e57b5..3711eb8f8e6 100644 --- a/test/lisp/erc/resources/base/upstream-reconnect/soju-barnet.eld +++ b/test/lisp/erc/resources/base/upstream-reconnect/soju-barnet.eld @@ -36,7 +36,7 @@ (0.06 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Once more I'll read the ode that I have writ.") (0.06 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: This is the foul fiend Flibbertigibbet: he begins at curfew, and walks till the first cock; he gives the web and the pin, squints the eye, and makes the harelip; mildews the white wheat, and hurts the poor creature of earth.") (0.06 ":joe!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :mike: Sir, I praise the Lord for you, and so may my parishioners; for their sons are well tutored by you, and their daughters profit very greatly under you: you are a good member of the commonwealth.") - (0.08 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: If it please your honour, I know not well what they are; but precise villains they are, that I am sure of, and void of all profanation in the world that good Christians ought to have.") + (0.08 ":mike!~u@6d9pasqcqwb2s.irc PRIVMSG #chan :joe: If it please your honor, I know not well what they are; but precise villains they are, that I am sure of, and void of all profanation in the world that good Christians ought to have.") ;; Unexpected disconnect (0.03 ":BouncerServ!BouncerServ@BouncerServ NOTICE tester :disconnected from barnet: failed to handle messages: failed to read IRC command: read tcp [::1]:54990->[::1]:6668: read: software caused connection abort") ;; Eventual reconnect diff --git a/test/lisp/erc/resources/erc-d/resources/incremental.eld b/test/lisp/erc/resources/erc-d/resources/incremental.eld index ab940fe6129..a1b48495ec3 100644 --- a/test/lisp/erc/resources/erc-d/resources/incremental.eld +++ b/test/lisp/erc/resources/erc-d/resources/incremental.eld @@ -30,7 +30,7 @@ ((mode 3 "MODE #foo") (0.0 ":irc.foo.net 324 tester #foo +Cint") (0.0 ":irc.foo.net 329 tester #foo 1519850102") - (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defence, by mercy, 'tis most just.") + (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defense, by mercy, 'tis most just.") (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: Grows, lives, and dies, in single blessedness.") (0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :Look for me.") (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.") diff --git a/test/lisp/erc/resources/erc-d/resources/no-block.eld b/test/lisp/erc/resources/erc-d/resources/no-block.eld index 1b1f3965637..2811923d8ac 100644 --- a/test/lisp/erc/resources/erc-d/resources/no-block.eld +++ b/test/lisp/erc/resources/erc-d/resources/no-block.eld @@ -36,7 +36,7 @@ ((mode-foo 1.2 "MODE #foo") (0.0 ":irc.example.org 324 tester #foo +Cint") (0.0 ":irc.example.org 329 tester #foo 1519850102") - (-0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defence, by mercy, 'tis most just.") + (-0.1 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: But, in defense, by mercy, 'tis most just.") (-0.2 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #foo :bob: Grows, lives, and dies, in single blessedness.") (-0.3 ":bob!~u@svpn88yjcdj42.irc PRIVMSG #foo :alice: For these two hours, Rosalind, I will leave thee.") (-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.") diff --git a/test/src/keymap-tests.el b/test/src/keymap-tests.el index de3012b5764..b0876664ed1 100644 --- a/test/src/keymap-tests.el +++ b/test/src/keymap-tests.el @@ -135,7 +135,7 @@ (should (eq (lookup-key map [menu-bar I-bar]) 'foo)))) (ert-deftest keymap-lookup-key/mixed-case-multibyte () - "Backwards compatibility behaviour (Bug#50752)." + "Backwards compatibility behavior (Bug#50752)." (let ((map (make-keymap))) ;; (downcase "Åäö") => "åäö" (define-key map [menu-bar åäö bar] 'foo) @@ -153,19 +153,19 @@ (should (eq (lookup-key map [menu-bar buffer 1]) 'foo)))) (ert-deftest keymap-lookup-keymap/with-spaces () - "Backwards compatibility behaviour (Bug#50752)." + "Backwards compatibility behavior (Bug#50752)." (let ((map (make-keymap))) (define-key map [menu-bar foo-bar] 'foo) (should (eq (lookup-key map [menu-bar Foo\ Bar]) 'foo)))) (ert-deftest keymap-lookup-keymap/with-spaces-multibyte () - "Backwards compatibility behaviour (Bug#50752)." + "Backwards compatibility behavior (Bug#50752)." (let ((map (make-keymap))) (define-key map [menu-bar åäö-bar] 'foo) (should (eq (lookup-key map [menu-bar Åäö\ Bar]) 'foo)))) (ert-deftest keymap-lookup-keymap/with-spaces-multibyte-lang-env () - "Backwards compatibility behaviour (Bug#50752)." + "Backwards compatibility behavior (Bug#50752)." (let ((lang-env current-language-environment)) (set-language-environment "Turkish") (let ((map (make-keymap))) -- cgit v1.2.3 From 41e612cfd684f03e34c8990df1b86b95e092211b Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Thu, 14 Jul 2022 12:43:52 +0200 Subject: ; Fix typos --- doc/lispref/commands.texi | 2 +- doc/lispref/variables.texi | 2 +- doc/misc/eshell.texi | 4 ++-- doc/misc/modus-themes.org | 4 ++-- etc/emacs_lldb.py | 2 +- lisp/doc-view.el | 2 +- lisp/emacs-lisp/loaddefs-gen.el | 4 ++-- lisp/face-remap.el | 2 +- src/haiku_support.h | 2 +- src/nsterm.m | 2 +- src/xfns.c | 2 +- src/xterm.c | 6 +++--- test/lisp/erc/resources/erc-d/erc-d-tests.el | 2 +- test/lisp/progmodes/python-tests.el | 2 +- test/manual/etags/cp-src/clheir.hpp | 2 +- test/manual/etags/merc-src/accumulator.m | 2 +- test/manual/etags/prol-src/natded.prolog | 2 +- test/manual/indent/perl.perl | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) (limited to 'test/lisp/erc/resources/erc-d') diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 865fa26b275..e37f6002430 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2078,7 +2078,7 @@ and @var{kind} as arguments. @item load-changed This xwidget event indicates that the @var{xwidget} has reached a particular point of the page-loading process. When these events are -sent, @var{arg} will contain a string that futher describes the status +sent, @var{arg} will contain a string that further describes the status of the widget: @table @samp diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index 13ad181cdd4..242b1a3be93 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -2843,7 +2843,7 @@ Common Lisp, this is not an error since the function @code{(setf it, that value won't be automatically restored. Users usually set normal variables in their startup files, or use Customize (@pxref{Customization}) to set user options permanently, and various -packages have various files wher they store the data (e.g., Gnus +packages have various files where they store the data (e.g., Gnus stores this in @file{.newsrc.eld} and the URL library stores cookies in @file{~/.emacs.d/url/cookies}). diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 963657f102a..9f9c88582f3 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1072,7 +1072,7 @@ back to a number as above). For example, @samp{$list("a" "b")c} returns @samp{("a" "bc")}. @item anything else -Concatenate the string represenation of each value. +Concatenate the string representation of each value. @end table @@ -1615,7 +1615,7 @@ nor the decoded data, into Emacs buffers, as would normally happen. The command is interpreted as extending up to the next @code{|} character which is not preceded by an unescaped asterisk following whitespace, or the end of the input if there is no such character. -Thus, all @code{<} and @code{>} redirections occuring before the next +Thus, all @code{<} and @code{>} redirections occurring before the next asterisk-unprefixed @code{|} are implicitly prefixed with (whitespace and) asterisks. An exception is that Eshell-specific redirects right at the end of the command are excluded. This allows input like this: diff --git a/doc/misc/modus-themes.org b/doc/misc/modus-themes.org index d0d985705f1..943294b626c 100644 --- a/doc/misc/modus-themes.org +++ b/doc/misc/modus-themes.org @@ -1252,7 +1252,7 @@ accepts is as follows (order is not significant): The ~popup~ key takes the same values as ~selection~. -Apart from specfying each key separately, a fallback list is accepted. +Apart from specifying each key separately, a fallback list is accepted. This is only useful when the desired aesthetic is the same across all keys that are not explicitly referenced. For example, this: @@ -3347,7 +3347,7 @@ it if you plan to control face attributes. :end: #+cindex: Org custom emphasis faces -Org provides the user option ~org-emphasis-alist~ which assosiates a +Org provides the user option ~org-emphasis-alist~ which associates a character with a face, list of faces, or face attributes. The default specification of that variable looks like this: diff --git a/etc/emacs_lldb.py b/etc/emacs_lldb.py index 740cbc0956b..b8530915f81 100644 --- a/etc/emacs_lldb.py +++ b/etc/emacs_lldb.py @@ -27,7 +27,7 @@ import lldb ######################################################################## -# Utilties +# Utilities ######################################################################## # Return the name of enumerator ENUM as a string. diff --git a/lisp/doc-view.el b/lisp/doc-view.el index 0f659fb8b37..f05ec938e55 100644 --- a/lisp/doc-view.el +++ b/lisp/doc-view.el @@ -238,7 +238,7 @@ Can be `dvi', `pdf', `ps', `djvu', `odf', `epub', `cbz', `fb2', (defun doc-view--epub-reconvert (&optional _event) "Reconvert all epub buffers. -EVENT is unused, but neccesary to work with the filenotify API" +EVENT is unused, but necessary to work with the filenotify API." (dolist (x (buffer-list)) (with-current-buffer x (when (eq doc-view-doc-type 'epub) diff --git a/lisp/emacs-lisp/loaddefs-gen.el b/lisp/emacs-lisp/loaddefs-gen.el index 2c92a8e7fe8..8a50b777da1 100644 --- a/lisp/emacs-lisp/loaddefs-gen.el +++ b/lisp/emacs-lisp/loaddefs-gen.el @@ -329,9 +329,9 @@ expression, in which case we want to handle forms differently." 'string<)))))) (defun loaddefs-generate--parse-file (file main-outfile &optional package-data) - "Examing FILE for ;;;###autoload statements. + "Examining FILE for ;;;###autoload statements. MAIN-OUTFILE is the main loaddefs file these statements are -destined for, but this can be overriden by the buffer-local +destined for, but this can be overridden by the buffer-local setting of `generated-autoload-file' in FILE, and by ;;;###foo-autoload statements. diff --git a/lisp/face-remap.el b/lisp/face-remap.el index fd49c81ab3f..432385587b4 100644 --- a/lisp/face-remap.el +++ b/lisp/face-remap.el @@ -481,7 +481,7 @@ used to invoke the command, with all modifiers removed: After adjusting, further adjust the font size as long as the key, with all modifiers removed, is one of the above characters. -Buffer-local face adjustements have higher priority than global +Buffer-local face adjustments have higher priority than global face adjustments. The variable `global-text-scale-adjust-resizes-frames' controls diff --git a/src/haiku_support.h b/src/haiku_support.h index 5f44494a8d3..5577d2f151f 100644 --- a/src/haiku_support.h +++ b/src/haiku_support.h @@ -398,7 +398,7 @@ struct haiku_font_pattern /* Temporary field used during font enumeration. */ int oblique_seen_p; - /* Whether or not to enable antialising in the font. This field is + /* Whether or not to enable antialiasing in the font. This field is special in that it's not handled by `BFont_open_pattern'. */ int use_antialiasing; }; diff --git a/src/nsterm.m b/src/nsterm.m index 8e0c4b84f0e..2ba167f189b 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -10917,7 +10917,7 @@ This variable is ignored on macOS < 10.7 and GNUstep. Default is t. */); It is called with three arguments FRAME, X, and Y, whenever the user moves the mouse over an Emacs frame as part of a drag-and-drop operation. FRAME is the frame the mouse is on top of, and X and Y are -the frame-relative positions of the mouse in the X and Y axises +the frame-relative positions of the mouse in the X and Y axes respectively. */); Vns_drag_motion_function = Qns_handle_drag_motion; diff --git a/src/xfns.c b/src/xfns.c index 331f22763ee..41a9d710d44 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -6963,7 +6963,7 @@ that mouse buttons are being held down, such as immediately after a xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionAsk; else if (SYMBOLP (action)) /* This is to accommodate non-standard DND protocols such as XDS - that are explictly implemented by Emacs, and is not documented + that are explicitly implemented by Emacs, and is not documented for that reason. */ xaction = symbol_to_x_atom (FRAME_DISPLAY_INFO (f), action); else if (CONSP (action)) diff --git a/src/xterm.c b/src/xterm.c index 85367b78e3c..2ccd6b1613d 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -1433,7 +1433,7 @@ static bool x_dnd_use_toplevels; /* Motif drag-and-drop protocol support. */ /* Pointer to a variable which stores whether or not an X error - occured while trying to create the Motif drag window. */ + occurred while trying to create the Motif drag window. */ static volatile bool *xm_drag_window_error; typedef enum xm_byte_order @@ -2308,7 +2308,7 @@ xm_get_drag_atom_1 (struct x_display_info *dpyinfo, &actual_format, &nitems, &bytes_remaining, &tmp_data); atom = None; - /* GCC thinks i is used unitialized, but it's always initialized if + /* GCC thinks i is used uninitialized, but it's always initialized if `atoms' exists at that particular spot. */ i = 0; @@ -28203,7 +28203,7 @@ you, try increasing the value of x_mouse_click_focus_ignore_position = false; DEFVAR_INT ("x-mouse-click-focus-ignore-time", x_mouse_click_focus_ignore_time, - doc: /* Number of miliseconds for which to ignore buttons after focus change. + doc: /* Number of milliseconds for which to ignore buttons after focus change. This variable only takes effect if `x-mouse-click-focus-ignore-position' is non-nil, and should be adjusted if the default value does not work for whatever reason. */); diff --git a/test/lisp/erc/resources/erc-d/erc-d-tests.el b/test/lisp/erc/resources/erc-d/erc-d-tests.el index f64b5e8a74c..21005cd7600 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-tests.el +++ b/test/lisp/erc/resources/erc-d/erc-d-tests.el @@ -1046,7 +1046,7 @@ nonzero for this to work." (kill-buffer client-buffer) (kill-buffer dumb-server-buffer)))) -;; Without adjusting penalty, takes ~15 secs. With is comprable to direct ^. +;; Without adjusting penalty, takes ~15 secs. With is comparable to direct ^. (ert-deftest erc-d-run-fuzzy () :tags '(:expensive-test) (let ((erc-server-flood-penalty 1.2) ; penalty < margin/sends is basically 0 diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el index d7b3c102f2d..92c20288c8e 100644 --- a/test/lisp/progmodes/python-tests.el +++ b/test/lisp/progmodes/python-tests.el @@ -1875,7 +1875,7 @@ class C(object): (python-tests-look-at "def m(self):" -1) (beginning-of-line) (point)))) - ;; Nested defuns shuld be skipped. + ;; Nested defuns should be skipped. (python-tests-look-at "return a" -1) (should (= (save-excursion (python-nav-beginning-of-defun) diff --git a/test/manual/etags/cp-src/clheir.hpp b/test/manual/etags/cp-src/clheir.hpp index 55d91228fb3..831da5ca095 100644 --- a/test/manual/etags/cp-src/clheir.hpp +++ b/test/manual/etags/cp-src/clheir.hpp @@ -2,7 +2,7 @@ /* CLHEIR.H */ /* ======================================================================= */ -// CLASS HEIRARCHY +// CLASS HIERARCHY // Locations or Agents are both of type generic_object. Generic_objects may // have states, and are responsible for updating their states appropriately // when their step() functions are executed. diff --git a/test/manual/etags/merc-src/accumulator.m b/test/manual/etags/merc-src/accumulator.m index c82dbf58ff8..0ee41216c11 100644 --- a/test/manual/etags/merc-src/accumulator.m +++ b/test/manual/etags/merc-src/accumulator.m @@ -616,7 +616,7 @@ identify_recursive_calls(PredId, ProcId, GoalStore, Ids) :- % Determine the variables which are members of the sets Out and Out', % and initialize the substitutions between the two sets. % - % This is done by identifing those variables whose instantiatedness change + % This is done by identifying those variables whose instantiatedness change % in the goals after the recursive call and are headvars. % % Note that we are only identifying the output variables which will need diff --git a/test/manual/etags/prol-src/natded.prolog b/test/manual/etags/prol-src/natded.prolog index f0ee6b41b12..08c81d48f61 100644 --- a/test/manual/etags/prol-src/natded.prolog +++ b/test/manual/etags/prol-src/natded.prolog @@ -371,7 +371,7 @@ derived_analyses([W|Ws],[W|DerWs]):- % build(Ws:+)>, Right:+, Left:-) % ---------------------------------------------------------------------- -% finishes building chart with Ws as remaing word, starting from +% finishes building chart with Ws as remaining word, starting from % right position Right and finishing on left position Left % -- counts backwards, so Left > Right % ---------------------------------------------------------------------- diff --git a/test/manual/indent/perl.perl b/test/manual/indent/perl.perl index db94552a928..b44593da028 100755 --- a/test/manual/indent/perl.perl +++ b/test/manual/indent/perl.perl @@ -90,7 +90,7 @@ s:abc:def:g; # FIXME: the initial s is fontified like a label, and indented s'def'ghi'g; # The middle ' should not end the quoting. s"ghi"ijk"g; # The middle ' should not end the quoting. -s#ijk#lmn#g; # This is a regular expression sustitution. +s#ijk#lmn#g; # This is a regular expression substitution. s #lmn#opq#g; # FIXME: this should be a comment starting with "#lmn" /lmn/rst/g; # and this is the actual regular expression -- cgit v1.2.3 From 69f578e04712616f91080ab26485db9faacb3a70 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 24 Jul 2022 05:14:24 -0700 Subject: Allow non-IRC line delimiters with ERC test server * test/lisp/erc/resources/erc-d/erc-d.el (erc-d-server-fqdn, erc-d--initialize-client, erc-d--log, erc-d--send, erc-d--filter, erc-d-run): Add new variable and use it. Also optionally accept keyword arguments in `erc-d-run'. * test/lisp/erc/resources/erc-d/erc-d-tests.el (erc-d-run-direct-foreign-protocol): Add test demoing newline-only line-wise protocol. * test/lisp/erc/resources/erc-d/resources/foreign.eld: New file. --- test/lisp/erc/resources/erc-d/erc-d-tests.el | 27 +++++++++ test/lisp/erc/resources/erc-d/erc-d.el | 68 +++++++++++++--------- .../lisp/erc/resources/erc-d/resources/foreign.eld | 5 ++ 3 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 test/lisp/erc/resources/erc-d/resources/foreign.eld (limited to 'test/lisp/erc/resources/erc-d') diff --git a/test/lisp/erc/resources/erc-d/erc-d-tests.el b/test/lisp/erc/resources/erc-d/erc-d-tests.el index 21005cd7600..357bc48b088 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-tests.el +++ b/test/lisp/erc/resources/erc-d/erc-d-tests.el @@ -1343,4 +1343,31 @@ DIALOGS are symbols representing the base names of dialog files in (kill-buffer dumb-server-buffer))) (delete-file sock)))) +(ert-deftest erc-d-run-direct-foreign-protocol () + :tags '(:expensive-test) + (let* ((server (erc-d-run "localhost" t "erc-d-server" 'foreign + :ending "\n")) + (server-buffer (get-buffer "*erc-d-server*")) + (client-buffer (get-buffer-create "*erc-d-client*")) + client) + (with-current-buffer server-buffer (erc-d-t-search-for 4 "Starting")) + (setq client (make-network-process + :buffer client-buffer + :name "erc-d-client" + :family 'ipv4 + :noquery t + :coding 'binary + :service (process-contact server :service) + :host "localhost")) + (process-send-string client "ONE one\n") + (with-current-buffer client-buffer + (erc-d-t-search-for 5 "echo ONE one")) + (process-send-string client "TWO two\n") + (with-current-buffer client-buffer + (erc-d-t-search-for 2 "echo TWO two")) + (erc-d-t-wait-for 2 "server death" (not (process-live-p server))) + (when noninteractive + (kill-buffer client-buffer) + (kill-buffer server-buffer)))) + ;;; erc-d-tests.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 index ee9b6a7fec9..d6082227c52 100644 --- a/test/lisp/erc/resources/erc-d/erc-d.el +++ b/test/lisp/erc/resources/erc-d/erc-d.el @@ -136,6 +136,9 @@ Only relevant when starting a server with `erc-d-run'.") Possibly used by overriding handlers, like the one for PING, and/or dialog templates for the sender portion of a reply message.") +(defvar erc-d-line-ending "\r\n" + "Protocol line delimiter for sending and receiving.") + (defvar erc-d-linger-secs nil "Seconds to wait before quitting for all dialogs. For more granular control, use the provided LINGER `rx' variable (alone) @@ -249,6 +252,7 @@ return a replacement.") (mat-h (copy-sequence (process-get process :dialog-match-handlers))) (fqdn (copy-sequence (process-get process :dialog-server-fqdn))) (vars (copy-sequence (process-get process :dialog-vars))) + (ending (process-get process :dialog-ending)) (dialog (make-erc-d-dialog :name name :process process :queue (make-ring 5) @@ -263,6 +267,8 @@ return a replacement.") (erc-d-dialog-hunks dialog) reader) ;; Add reverse link, register client, launch (process-put process :dialog dialog) + (process-put process :ending ending) + (process-put process :ending-regexp (rx-to-string `(+ ,ending))) (push process erc-d--clients) (erc-d--command-refresh dialog nil) (erc-d--on-request process))) @@ -311,7 +317,7 @@ PROCESS should be a client connection or a server network process." (name (erc-d-dialog-name (process-get ,process :dialog)))) (if ,outbound (erc-d--m process "-> %s:%s %s" name id ,string) - (dolist (line (split-string ,string "\r\n")) + (dolist (line (split-string ,string (process-get process :ending))) (erc-d--m process "<- %s:%s %s" name id line))))) (defun erc-d--log-process-event (server process msg) @@ -320,7 +326,7 @@ PROCESS should be a client connection or a server network process." (defun erc-d--send (process string) "Send STRING to PROCESS peer." (erc-d--log process string 'outbound) - (process-send-string process (concat string "\r\n"))) + (process-send-string process (concat string (process-get process :ending)))) (define-inline erc-d--fuzzy-p (exchange) (inline-letevals (exchange) @@ -442,9 +448,10 @@ This will start the teardown for DIALOG." "Handle input received from peer. PROCESS represents a client peer connection and STRING is a raw request including line delimiters." - (let ((queue (erc-d-dialog-queue (process-get process :dialog)))) + (let ((queue (erc-d-dialog-queue (process-get process :dialog))) + (delim (process-get process :ending-regexp))) (setq string (concat (process-get process :stashed-input) string)) - (while (and string (string-match (rx (+ "\r\n")) string)) + (while (and string (string-match delim string)) (let ((line (substring string 0 (match-beginning 0)))) (setq string (unless (= (match-end 0) (length string)) (substring string (match-end 0)))) @@ -913,35 +920,40 @@ Pass HOST and SERVICE directly to `make-network-process'. When present, use string SERVER-NAME for the server-process name as well as that of its buffer (w. surrounding asterisks). When absent, do the same with `erc-d-server-name'. When running \"in process,\" return the server -process, otherwise sleep for the duration of the server process. +process; otherwise sleep until it dies. A dialog must be a symbol matching the base name of a dialog file in -`erc-d-u-canned-dialog-dir'. - -The variable `erc-d-tmpl-vars' determines the common members of the -`erc-d--render-entries' ENTRIES param. Variables `erc-d-server-fqdn' -and `erc-d-linger-secs' determine the `erc-d-dialog' items -`:server-fqdn' and `:linger-secs' for all client processes. - -The variable `erc-d-tmpl-vars' can be used to initialize the -process's `erc-d-dialog' vars item." +`erc-d-u-canned-dialog-dir'. Global variables `erc-d-server-fqdn', +`erc-d-linger-secs', and `erc-d-tmpl-vars' determine the process's +`erc-d-dialog' fields `:server-fqdn', `:linger-secs', and `:vars', +respectively. The latter may also be populated via keyword pairs +appearing among DIALOGS." (when (and server-name (symbolp server-name)) (push server-name dialogs) (setq server-name nil)) - (let (loaded) - (dolist (dialog (nreverse dialogs)) - (let ((reader (erc-d-u--canned-load-dialog dialog))) - (when erc-d--slow-mo - (setq reader (erc-d-u--rewrite-for-slow-mo erc-d--slow-mo reader))) - (push (cons (erc-d-u--normalize-canned-name dialog) reader) loaded))) - (setq dialogs loaded)) - (erc-d--start host service (or server-name erc-d-server-name) - :dialog-dialogs dialogs - :dialog-vars erc-d-tmpl-vars - :dialog-linger-secs erc-d-linger-secs - :dialog-server-fqdn erc-d-server-fqdn - :dialog-match-handlers (erc-d-u--unkeyword - erc-d-match-handlers))) + (let (loaded kwds defaults args) + (while dialogs + (if-let* ((dlog (pop dialogs)) + ((keywordp dlog))) + (progn (push (pop dialogs) kwds) (push dlog kwds)) + (let ((reader (erc-d-u--canned-load-dialog dlog))) + (when erc-d--slow-mo + (setq reader (erc-d-u--rewrite-for-slow-mo erc-d--slow-mo reader))) + (push (cons (erc-d-u--normalize-canned-name dlog) reader) loaded)))) + (setq kwds (erc-d-u--unkeyword kwds) + defaults `((ending . ,erc-d-line-ending) + (server-fqdn . ,erc-d-server-fqdn) + (linger-secs . ,erc-d-linger-secs) + (vars . ,(or (plist-get kwds 'tmpl-vars) erc-d-tmpl-vars)) + (dialogs . ,(nreverse loaded))) + args (list :dialog-match-handlers + (erc-d-u--unkeyword (or (plist-get kwds 'match-handlers) + erc-d-match-handlers)))) + (pcase-dolist (`(,var . ,def) defaults) + (push (or (plist-get kwds var) def) args) + (push (intern (format ":dialog-%s" var)) args)) + (apply #'erc-d--start host service (or server-name erc-d-server-name) + args))) (defun erc-d-serve () "Start serving canned dialogs from the command line. diff --git a/test/lisp/erc/resources/erc-d/resources/foreign.eld b/test/lisp/erc/resources/erc-d/resources/foreign.eld new file mode 100644 index 00000000000..64a5dca8b10 --- /dev/null +++ b/test/lisp/erc/resources/erc-d/resources/foreign.eld @@ -0,0 +1,5 @@ +;;; -*- mode: lisp-data -*- +((one 5 "ONE one") + (0 "echo ONE one")) +((two 5 "TWO two") + (0 "echo TWO two")) -- cgit v1.2.3 From 33fdb1daa354e5045e6e4a798db18d2ba1fbc38b Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Sep 2022 14:42:01 -0700 Subject: ; Tag some ERC test-server tests as being :unstable * test/lisp/erc/resources/base/assoc/samenet/chester.eld: Relax timeout. * test/lisp/erc/resources/base/assoc/samenet/tester.eld: Relax timeout. * test/lisp/erc/resources/base/assoc/samenet/tester2.eld: Relax timeout. * test/lisp/erc/resources/base/netid/samenet/chester.eld: Relax timeout. * test/lisp/erc/resources/base/netid/samenet/tester.eld: Relax timeout. * test/lisp/erc/resources/erc-d/erc-d-tests.el (erc-d-run-linger, erc-d-run-linger-fail, erc-d-run-linger-direct): Mark some tests as being unstable. --- test/lisp/erc/resources/base/assoc/samenet/chester.eld | 2 +- test/lisp/erc/resources/base/assoc/samenet/tester.eld | 2 +- test/lisp/erc/resources/base/assoc/samenet/tester2.eld | 2 +- test/lisp/erc/resources/base/netid/samenet/chester.eld | 2 +- test/lisp/erc/resources/base/netid/samenet/tester.eld | 2 +- test/lisp/erc/resources/erc-d/erc-d-tests.el | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) (limited to 'test/lisp/erc/resources/erc-d') diff --git a/test/lisp/erc/resources/base/assoc/samenet/chester.eld b/test/lisp/erc/resources/base/assoc/samenet/chester.eld index f1aed2836c7..0132de677cb 100644 --- a/test/lisp/erc/resources/base/assoc/samenet/chester.eld +++ b/test/lisp/erc/resources/base/assoc/samenet/chester.eld @@ -1,5 +1,5 @@ ;; -*- mode: lisp-data; -*- -((pass 1 "PASS :changeme")) +((pass 10 "PASS :changeme")) ((nick 1 "NICK chester")) ((user 1 "USER user 0 * :chester") (0 ":irc.foonet.org 001 chester :Welcome to the foonet IRC Network chester") diff --git a/test/lisp/erc/resources/base/assoc/samenet/tester.eld b/test/lisp/erc/resources/base/assoc/samenet/tester.eld index cd9cacbe5dc..995fab00f7d 100644 --- a/test/lisp/erc/resources/base/assoc/samenet/tester.eld +++ b/test/lisp/erc/resources/base/assoc/samenet/tester.eld @@ -1,5 +1,5 @@ ;; -*- mode: lisp-data; -*- -((pass 1 "PASS :changeme")) +((pass 10 "PASS :changeme")) ((nick 1 "NICK tester")) ((user 1 "USER user 0 * :tester") (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") diff --git a/test/lisp/erc/resources/base/assoc/samenet/tester2.eld b/test/lisp/erc/resources/base/assoc/samenet/tester2.eld index 67c3a94a262..33a05fe2611 100644 --- a/test/lisp/erc/resources/base/assoc/samenet/tester2.eld +++ b/test/lisp/erc/resources/base/assoc/samenet/tester2.eld @@ -1,5 +1,5 @@ ;; -*- mode: lisp-data; -*- -((pass 1 "PASS :changeme")) +((pass 10 "PASS :changeme")) ((nick 1 "NICK tester")) ((user 1 "USER user 0 * :tester") (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") diff --git a/test/lisp/erc/resources/base/netid/samenet/chester.eld b/test/lisp/erc/resources/base/netid/samenet/chester.eld index 8c2448733ce..7b4bfee9c9a 100644 --- a/test/lisp/erc/resources/base/netid/samenet/chester.eld +++ b/test/lisp/erc/resources/base/netid/samenet/chester.eld @@ -1,5 +1,5 @@ ;; -*- mode: lisp-data; -*- -((pass 1 "PASS :changeme")) +((pass 10 "PASS :changeme")) ((nick 1 "NICK chester")) ((user 1 "USER user 0 * :chester") (0 ":irc.foonet.org 001 chester :Welcome to the foonet IRC Network chester") diff --git a/test/lisp/erc/resources/base/netid/samenet/tester.eld b/test/lisp/erc/resources/base/netid/samenet/tester.eld index 76312a7a14a..f41b041db4b 100644 --- a/test/lisp/erc/resources/base/netid/samenet/tester.eld +++ b/test/lisp/erc/resources/base/netid/samenet/tester.eld @@ -1,5 +1,5 @@ ;; -*- mode: lisp-data; -*- -((pass 1 "PASS :changeme")) +((pass 10 "PASS :changeme")) ((nick 1 "NICK tester")) ((user 1 "USER user 0 * :tester") (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") diff --git a/test/lisp/erc/resources/erc-d/erc-d-tests.el b/test/lisp/erc/resources/erc-d/erc-d-tests.el index 357bc48b088..a4befd96b5e 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-tests.el +++ b/test/lisp/erc/resources/erc-d/erc-d-tests.el @@ -673,7 +673,7 @@ nonzero for this to work." (cadr (pop errors)))))) (ert-deftest erc-d-run-linger () - :tags '(:expensive-test) + :tags '(:unstable :expensive-test) (erc-d-tests-with-server (dumb-s _) linger (with-current-buffer (erc-d-t-wait-for 6 (get-buffer "#chan")) (erc-d-t-search-for 2 "hey")) @@ -683,7 +683,7 @@ nonzero for this to work." (erc-d-t-search-for 3 "Lingered for 1.00 seconds")))) (ert-deftest erc-d-run-linger-fail () - :tags '(:expensive-test) + :tags '(:unstable :expensive-test) (let ((erc-server-flood-penalty 0.1) errors) (erc-d-tests-with-failure-spy @@ -696,7 +696,7 @@ nonzero for this to work." (should (string-match-p "Match failed.*hi" (cadr (pop errors)))))) (ert-deftest erc-d-run-linger-direct () - :tags '(:expensive-test) + :tags '(:unstable :expensive-test) (let* ((dumb-server (erc-d-run "localhost" t 'linger-multi-a 'linger-multi-b)) (port (process-contact dumb-server :service)) -- cgit v1.2.3