;;; erc-d-self.el --- tests for erc-d -*- lexical-binding: t -*- ;; 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 file tests the dumb server itself. The file name does not end ;; in "-tests.el" because test/Makefile looks for corresponding ;; library files and raises an error when one isn't found. ;;; Code: (require 'ert-x) (eval-and-compile (let ((dir (getenv "EMACS_TEST_DIRECTORY"))) (when dir (load (concat dir "/lisp/erc/erc-d/erc-d") nil t) (load (concat dir "/lisp/erc/erc-d/erc-d-t") nil t)))) (require 'erc-d) (require 'erc-d-t) (require 'erc-backend) (ert-deftest erc-d-u--canned-load-dialog--basic () (should-not (get-buffer "basic.lispdata")) (should-not erc-d-u--canned-buffers) (let ((exes (erc-d-u--canned-load-dialog 'basic t))) (should (get-buffer "basic.lispdata")) (should (memq (get-buffer (get-buffer "basic.lispdata")) erc-d-u--canned-buffers)) (should (equal (cl-loop for spec iter-by (iter-next exes) collect spec) '((pass 10.0 "PASS " (? ?:) "changeme")))) (should (equal (cl-loop for spec iter-by (iter-next exes) collect spec) '((nick 0.2 "NICK tester")))) (should (equal (car (cl-loop for s iter-by (iter-next exes) collect s)) '(user 0.2 "USER user 0 * :tester"))) (should (equal (car (cl-loop for s iter-by (iter-next exes) collect s)) '(mode-user 1.2 "MODE tester +i"))) (should (equal (cl-loop for s iter-by (iter-next exes) collect s) '((mode-chan 1.2 "MODE #chan") (0.1 ":bob!~bob@example.org PRIVMSG #chan :hey")))) ;; See `define-error' site for `iter-end-of-sequence' ;; Guess this self-parenting prevents detection by `should-error' (ert-info ("End of buffer detected") (let (done) (condition-case err (iter-next exes) (iter-end-of-sequence (setq done (cdr err)))) (should-not done)))) (should-not (get-buffer "basic.lispdata")) (should-not erc-d-u--canned-buffers)) ;; 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.lispdata")) (should-not erc-d-u--canned-buffers) (let* ((exes (erc-d-u--canned-load-dialog 'basic t)) (pass (iter-next exes)) (nick (iter-next exes)) (user (iter-next exes)) (modu (iter-next exes)) (modc (iter-next exes))) (should (equal (iter-next user) '(user 0.2 "USER user 0 * :tester"))) (should (equal (iter-next modu) '(mode-user 1.2 "MODE tester +i"))) (should (equal (iter-next modc) '(mode-chan 1.2 "MODE #chan"))) (cl-loop repeat 8 do (iter-next user)) ; skip a few (should (equal (iter-next user) '(0 ":irc.example.org 254 tester 1 :channels formed"))) (should (equal (iter-next modu) '(0 ":irc.example.org 221 tester +Zi"))) (should (equal (cl-loop for spec iter-by modc collect spec) ; done '((0.1 ":bob!~bob@example.org PRIVMSG #chan :hey")))) (cl-loop repeat 3 do (iter-next user)) (cl-loop repeat 3 do (iter-next modu)) (ert-info ("Change up the order") (should (equal (iter-next modu) '(0 ":irc.example.org 366 alice #chan :End of NAMES list"))) (should (equal (iter-next user) '(0 ":irc.example.org 422 tester :MOTD File is missing")))) ;; Exhaust these (should (equal (cl-loop for spec iter-by pass collect spec) ; done '((pass 10.0 "PASS " (? ?:) "changeme")))) (should (equal (cl-loop for spec iter-by nick collect spec) ; done '((nick 0.2 "NICK tester")))) (ert-info ("End of file but no teardown because hunks outstanding") (condition-case err (iter-next exes) (iter-end-of-sequence (should-not (cdr err)))) (should (get-buffer "basic.lispdata"))) ;; Finish (should (eq t (condition-case _ (iter-next user) (iter-end-of-sequence t)))) (should (eq t (condition-case _ (iter-next modu) (iter-end-of-sequence t))))) (should-not (get-buffer "basic.lispdata")) (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.lispdata")) (should-not (get-buffer "basic.lispdata<2>")) (should-not (get-buffer "basic.lispdata<3>")) (should-not erc-d-u--canned-buffers) (let ((exes (erc-d-u--canned-load-dialog 'basic)) ; genny in (exes-lower (erc-d-u--canned-load-dialog 'basic t)) ; iter in (exes-custom (erc-d-u--canned-load-dialog 'basic t))) (should (get-buffer "basic.lispdata")) (should (get-buffer "basic.lispdata<2>")) (should (get-buffer "basic.lispdata<3>")) (should (equal (list (get-buffer "basic.lispdata<3>") (get-buffer "basic.lispdata<2>") (get-buffer "basic.lispdata")) erc-d-u--canned-buffers)) (ert-info ("Rewrite for slowmo basic") (setq exes (funcall (erc-d-u--rewrite-for-slow-mo 10 exes))) ; genny out (should (equal (cl-loop for s iter-by (iter-next exes) collect s) '((pass 20.0 "PASS " (? ?:) "changeme")))) (should (equal (cl-loop for s iter-by (iter-next exes) collect s) '((nick 10.2 "NICK tester")))) (should (equal (car (cl-loop for s iter-by (iter-next exes) collect s)) '(user 10.2 "USER user 0 * :tester"))) (should (equal (car (cl-loop for s iter-by (iter-next exes) collect s)) '(mode-user 11.2 "MODE tester +i"))) (should (equal (car (cl-loop for s iter-by (iter-next exes) collect s)) '(mode-chan 11.2 "MODE #chan"))) (ert-info ("End of buffer detected") (let (done) (condition-case err (iter-next exes) (iter-end-of-sequence (setq done (cdr err)))) (should-not done)))) (ert-info ("Rewrite for slowmo bounded") (setq exes-lower (erc-d-u--rewrite-for-slow-mo -5 exes-lower)) ; iter out (should (equal (cl-loop for s iter-by (iter-next exes-lower) collect s) '((pass 10.0 "PASS " (? ?:) "changeme")))) (should (equal (cl-loop for s iter-by (iter-next exes-lower) collect s) '((nick 5 "NICK tester")))) (should (equal (car (cl-loop for s iter-by (iter-next exes-lower) collect s)) '(user 5 "USER user 0 * :tester"))) (should (equal (car (cl-loop for s iter-by (iter-next exes-lower) collect s)) '(mode-user 5 "MODE tester +i"))) (should (equal (car (cl-loop for s iter-by (iter-next exes-lower) collect s)) '(mode-chan 5 "MODE #chan"))) (should-not (ignore-error iter-end-of-sequence (iter-next 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 (cl-loop for s iter-by (iter-next exes-custom) collect s) '((pass 20.0 "PASS " (? ?:) "changeme")))) (should (equal (cl-loop for s iter-by (iter-next exes-custom) collect s) '((nick 0.4 "NICK tester")))) (should (equal (car (cl-loop for s iter-by (iter-next exes-custom) collect s)) '(user 0.4 "USER user 0 * :tester"))) (should (equal (car (cl-loop for s iter-by (iter-next exes-custom) collect s)) '(mode-user 2.4 "MODE tester +i"))) (should (equal (car (cl-loop for s iter-by (iter-next exes-custom) collect s)) '(mode-chan 2.4 "MODE #chan"))) (should-not (ignore-error iter-end-of-sequence (iter-next exes-custom))))) (should-not (get-buffer "basic.lispdata")) (should-not (get-buffer "basic.lispdata<2>")) (should-not (get-buffer "basic.lispdata<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-self--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-self--parse-message-upstream raw)) (ours (erc-d-i--parse-message raw))) ;; Upstream tags may be nil or (SYM . VAL) or (STR VAL) depending on ;; the Emacs version and whether erc-v3 has been loaded. (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 (ert-resource-file "irc-parser-tests.lispdata")) (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)))))) (iter-defun erc-d-self--i (form) (while form (iter-yield (pop form)))) (ert-deftest erc-d--render-entries () (let ((dialog (make-erc-d-dialog :vars `((:a . 1) (c . ((a b) (: a space b))) (d . (c alpha digit)) (bee . 2) (f . ,(lambda () "3")) (i . emacs-pid)))) (exchange (make-erc-d-exchange)) it) (erc-d-exchange-reload dialog exchange) (ert-info ("Baseline Outgoing") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 "abc"))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "abc"))) (ert-info ("Incoming are regexp escaped") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((i 0.0 "fsf" ".org"))))) (should (equal (cons (iter-next it) (iter-next it)) '(i . 0.0))) (should (equal (iter-next it) "\\`fsf\\.org"))) (ert-info ("Incoming can access vars via rx-let") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((i 0.0 bee))))) (should (equal (cons (iter-next it) (iter-next it)) '(i . 0.0))) (should (equal (iter-next it) "\\`\002"))) (ert-info ("Incoming rx-let params") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((i 0.0 d))))) (should (equal (cons (iter-next it) (iter-next it)) '(i . 0.0))) (should (equal (iter-next it) "\\`[[:alpha:]][[:space:]][[:digit:]]"))) (ert-info ("Incoming literal rx forms") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((i 0.0 (= 3 alpha) ".org"))))) (should (equal (cons (iter-next it) (iter-next it)) '(i . 0.0))) (should (equal (iter-next it) "\\`[[:alpha:]]\\{3\\}\\.org"))) (ert-info ("Self-quoting disallowed") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 :a "abc"))))) (should (equal (iter-next it) 0)) (should-error (iter-next it))) (ert-info ("Outgoing mixed") (let ((s '((0 (format "%s" (not (zerop i))) (string bee) f)))) (setq it (erc-d--render-entries dialog exchange (erc-d-self--i s)))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "t\0023"))) (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)))))) (let (c) (condition-case _ (iter-next it) (iter-end-of-sequence (setq c t))) (should c)) (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 . emacs-pid))))))) (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 :request "foo bar baz" ;; 11 222 :match-data '(4 11 4 6 8 11))) it) (erc-d-exchange-reload dialog exchange) (ert-info ("Baseline outgoing") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 :request))))) (should (equal (iter-next it) 0)) (should-error (iter-next it))) (ert-info ("One arg, match") (erc-d-exchange-reload dialog exchange) (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 f))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "ba"))) (ert-info ("No args") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 g))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "baz"))) (ert-info ("Second arg is exchange object") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 i))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "foo bar baz"))) (ert-info ("One arg, multiple calls") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 h))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "bar bazfoo bar baz"))) (ert-info ("Match data restored") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 j))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "j")) (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 g))))) (should (equal (iter-next it) 0)) (should (equal (iter-next 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") '(f . (lambda () foo)) (cons 'g '(lambda (a) (funcall a :rebind 'g 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)) it) (erc-d-exchange-reload dialog exchange) (ert-info ("Initial reference calls function") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 j) (0 j))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "abc"))) (ert-info ("Subsequent reference expands to string") (should (equal (iter-next it) 0)) (should (equal (iter-next it) "123"))) (ert-info ("Outside manipulation: initial reference calls function") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 k) (0 k))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "abc"))) (ert-info ("Outside manipulation: subsequent reference expands to string") (erc-d-exchange-rebind dialog exchange 'k "123") (should (equal (iter-next it) 0)) (should (equal (iter-next it) "123"))) (ert-info ("Swap one function for another") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 g) (0 g))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "bar")) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "foo"))) (ert-info ("Bindings accessible inside functions") ; anti-feature? (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 f))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "foo"))) (ert-info ("Rebuild alist by sending flag") (setq it (erc-d--render-entries dialog exchange (erc-d-self--i '((0 f) (1 f) (2 f) (i 3 f))))) (should (equal (iter-next it) 0)) (should (equal (iter-next it) "foo")) (erc-d-exchange-rebind dialog exchange 'f "bar") (should (equal (iter-next it) 1)) (should (equal (iter-next it) "bar")) (setq alist (setf (alist-get 'f (erc-d-dialog-vars dialog)) (lambda nil "baz"))) (should (eq (iter-next it) 2)) (should (equal (iter-next it 'reload) "baz")) (setq alist (setf (alist-get 'f (erc-d-dialog-vars dialog)) "spam")) (should (eq (iter-next it) 'i)) (should (eq (iter-next it 'reload) 3)) (should (equal (iter-next 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*"))) (eval-and-compile (defvar erc-d-self-with-server-password "changeme")) ;; Compromise between removing `autojoin' from `erc-modules' entirely ;; and allowing side effects to meddle excessively (defvar erc-autojoin-channels-alist) ;; This is only meant to be used by tests in this file. (cl-defmacro erc-d-self-with-server ((dumb-server-var erc-server-buffer-var) dialog &rest body) "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-self-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-self-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 (cl-labels ((ad (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)) (setq ,found (nreverse ,found)) (dolist (sym ,func-syms) (advice-remove sym 'spy)))) (ert-deftest erc-d-run-nonstandard-messages () (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)) (erc-d-t-use-regeexp-when-searching t) 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$") (funcall expect 1 "<- 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 "<- nonstandard:[[:digit:]]+ $"))) (ert-info ("Server preserves spaces") (process-send-string client " \r\n") (with-current-buffer dumb-server-buffer (funcall expect 1 "<- nonstandard:[[:digit:]]+ \\{2\\}$")) (process-send-string client " \r\n") (with-current-buffer dumb-server-buffer (funcall expect 1 "<- 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 () (erc-d-self-with-server (_ _) basic (erc-d-t-wait-for 3 "#chan" (get-buffer "#chan")) (with-current-buffer "#chan" (erc-d-t-search-for 2 "hey")) (when noninteractive (kill-buffer "#chan")))) (ert-deftest erc-d-run-eof () (skip-unless noninteractive) (erc-d-self-with-server (_ erc-s-buf) eof (erc-d-t-wait-for 3 "#chan" (get-buffer "#chan")) (with-current-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 () (let (errors) (erc-d-self-with-failure-spy errors '(erc-d--teardown) (erc-d-self-with-server (_ _) eof (erc-d-t-wait-for 5 "#chan" (get-buffer "#chan")) (with-current-buffer "#chan" (erc-d-t-search-for 2 "hey")) (erc-d-t-wait-for 10 "Bad match" errors))) (should (string-match-p "Timed out awaiting request.*__EOF__" (cadr (pop errors)))))) (ert-deftest erc-d-run-linger () (erc-d-self-with-server (dumb-s _) linger (erc-d-t-wait-for 3 "#chan" (get-buffer "#chan")) (with-current-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 2 "Lingered for 1.00 seconds")))) (ert-deftest erc-d-run-linger-fail () (let ((erc-server-flood-penalty 0.1) errors) (erc-d-self-with-failure-spy errors '(erc-d--teardown erc-d-command) (erc-d-self-with-server (_ _) linger (erc-d-t-wait-for 3 "#chan" (get-buffer "#chan")) (with-current-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 () (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 () (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 () (let ((erc-d-linger-secs 1) erc-server-auto-reconnect errors) (erc-d-self-with-failure-spy errors '(erc-d--teardown erc-d-command) (erc-d-self-with-server (_ erc-server-buffer) no-match (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 () (let ((erc-d-linger-secs 1) err errors) (erc-d-self-with-failure-spy errors '(erc-d--teardown) (erc-d-self-with-server (_ _) timeout (erc-d-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 () (let ((erc-d-linger-secs 2) errors) (erc-d-self-with-failure-spy errors '(erc-d--teardown erc-d-command) (erc-d-self-with-server (_ _) unexpected (ert-info ("All specs consumed when more input arrives") (erc-d-t-wait-for 10 "error caught" (= 2 (length 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 () (let ((erc-d-linger-secs 3) errors) (erc-d-self-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 "Connection")) (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) (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-self--dynamic-match-user (_dialog exchange) "Shared pattern/response handler for canned dynamic DIALOG test." (should (string= (match-string 1 (erc-d-exchange-request exchange)) "tester"))) (defun erc-d-self--run-dynamic () "Perform common assertions for \"dynamic\" dialog." (erc-d-self-with-server (dumb-server erc-server-buffer) dynamic (erc-d-t-wait-for 3 "#chan" (get-buffer "#chan")) (with-current-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 () (let* (dynamic-tally (erc-d-spec-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-self--dynamic-match-user d e)) :mode-user (lambda (d e) (erc-d-exchange-rebind d e 'nick "tester") (erc-d-exchange-rebind d e 'dom dom)))) (erc-d-server-fqdn "irc.fsf.org")) (erc-d-self--run-dynamic) (should (equal '((dom . match-user) (nick . match-user) (dom . match-user)) dynamic-tally)))) (ert-deftest erc-d-run-dynamic-default-match-rebind () (let* (tally ;; (erc-d-spec-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-self--dynamic-match-user d e)) :mode-user (lambda (d e) (erc-d-exchange-rebind d e 'nick "tester") (erc-d-exchange-rebind d e 'dom erc-d-server-fqdn)))) (erc-d-server-fqdn "irc.fsf.org")) (erc-d-self--run-dynamic) (should (equal '(bind-nick bind-dom) tally)))) (ert-deftest erc-d-run-dynamic-runtime-stub () (let ((erc-d-spec-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-self-with-server-password "foonet:changeme")) (erc-d-self-with-server (_ erc-server-buffer) (dynamic-stub dynamic-foonet) (erc-d-t-wait-for 3 "#chan" (get-buffer "#chan")) (with-current-buffer "#chan" (erc-d-t-search-for 2 "alice:") (erc-d-t-search-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 () (let ((erc-d-spec-vars '((token . "barnet"))) (erc-d-match-handlers (list :pass (lambda (d _e) (erc-d-load-replacement-dialog d 'dynamic-barnet 1)))) (erc-d-self-with-server-password "barnet:changeme")) (erc-d-self-with-server (_ erc-server-buffer) (dynamic-stub dynamic-barnet) (erc-d-t-wait-for 3 "#chan" (get-buffer "#chan")) (with-current-buffer "#chan" (erc-d-t-search-for 2 "joe:") (erc-d-t-search-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"))))) ;; This can be removed; only exists to get a baseline for next test (ert-deftest erc-d-run-fuzzy-direct () (let* ((erc-d-linger-secs 0.1) (erc-d-spec-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 () (let ((erc-server-flood-penalty 1.2) ; penalty < margin/sends is basically 0 (erc-d-linger-secs 0.1) (erc-d-spec-vars `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t))))) erc-server-auto-reconnect) (erc-d-self-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 "#bar" (get-buffer "#bar")) (with-current-buffer erc-server-buffer (erc-cmd-JOIN "#foo")) (erc-d-t-wait-for 20 "#foo" (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 () (let ((erc-server-flood-penalty 1) (erc-d-linger-secs 1.2) (expect (erc-d-t-make-expecter)) erc-server-auto-reconnect) (erc-d-self-with-server (_ erc-server-buffer) no-block (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"))) (erc-d-t-wait-for 2 "#foo" (get-buffer "#foo")) (with-current-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 "#bar" (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")) (with-current-buffer "#bar" (funcall expect 1 "hi")) (with-current-buffer "#foo" (should-not (search-forward " I am heard" nil t)) (funcall expect 1.5 " I am heard")))))) (defun erc-d-self--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 "Connection")) (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-spec-vars' before ;; starting the server. (ert-deftest erc-d-run-proxy-direct-spec-vars () (let* ((dumb-server-buffer (get-buffer-create "*erc-d-server*")) (erc-d-linger-secs 0.5) (erc-d-spec-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-self--run-proxy-direct dumb-server dumb-server-buffer port))) (cl-defun erc-d-self--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 () (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-spec-vars '((fqdn . ,fqdn) (net . ,net) (network . (group (+ alpha)))))) (port (erc-d-self--start-server :linger 0.3 :program program :buffer buffer :dialogs '(proxy-foonet proxy-barnet))) (server (pop port))) (erc-d-self--run-proxy-direct server buffer port))) (ert-deftest erc-d-run-proxy-direct-subprocess-lib () (let* ((buffer (get-buffer-create "*erc-d-server*")) (lib (ert-resource-file "proxy-subprocess.el")) (port (erc-d-self--start-server :linger 0.3 :buffer buffer :dialogs '(proxy-foonet proxy-barnet) :libs (list lib))) (server (pop port))) (erc-d-self--run-proxy-direct server buffer port))) (ert-deftest erc-d-run-no-pong () (let* (erc-d-auto-pong ;; (erc-d-spec-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 "Connection")) (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 () (let ((erc-server-flood-penalty 0) (expect (erc-d-t-make-expecter)) erc-d-linger-secs) (erc-d-self-with-server (_ erc-server-buffer) incremental (with-current-buffer erc-server-buffer (funcall expect 3 "marked as being away")) (with-current-buffer erc-server-buffer (erc-cmd-JOIN "#foo")) (erc-d-t-wait-for 1 "#foo exists" (get-buffer "#foo")) (with-current-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 () (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-self.el ends here