emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] scratch/flymake-refactor b327de6 48/52: Flymake backends c


From: João Távora
Subject: [Emacs-diffs] scratch/flymake-refactor b327de6 48/52: Flymake backends can report multiple times per check
Date: Sun, 1 Oct 2017 12:40:52 -0400 (EDT)

branch: scratch/flymake-refactor
commit b327de63132fe4272ab694107b2dfa45dbe49f32
Author: João Távora <address@hidden>
Commit: João Távora <address@hidden>

    Flymake backends can report multiple times per check
    
    Rewrote a significant part of the Flymake backend API.  Flymake now
    ignores the return value of backend functions: a function can either
    returns or errors.  If it doesn't error, a backend is no longer
    constrained to call REPORT-FN exactly once.  It may do so any number
    of times, cumulatively reporting diagnostics.  Flymake keeps track of
    outdated REPORT-FN instances and disconsiders obsolete reports.
    Backends should avoid reporting obsolete data by cancelling any
    ongoing processing at every renewed call to the backend function.
    
    Consolidated flymake-ui.el internal data structures to require less
    buffer-local variables.  Adjusted Flymake's mode-line indicator to the
    new semantics.
    
    Adapted and simplified the implementation of elisp and legacy
    backends, fixing potential race conditions when calling backends in
    rapid succession.
    
    Added a new test for a backend that calls REPORT-FN multiple
    times.  Simplify test infrastructure.
    
    * lisp/progmodes/flymake-elisp.el (flymake-elisp-checkdoc)
    (flymake-elisp-byte-compile): Error instead of returning nil
    if not in emacs-lisp-mode.
    (flymake-elisp--byte-compile-process): New buffer-local variable.
    (flymake-elisp-byte-compile): Mark (and kill) previous process
    obsolete process before starting a new one.  Don't report if
    obsolete process.
    
    * lisp/progmodes/flymake-proc.el
    (flymake-proc--current-process): New buffer-local variable.
    (flymake-proc--processes): Remove.
    (flymake-proc--process-filter): Don't bind
    flymake-proc--report-fn.
    (flymake-proc--process-sentinel): Rewrite.  Don't report if
    obsolete process.
    (flymake-proc-legacy-flymake): Rewrite.  Mark (and kill)
    previous process obsolete process before starting a new
    one.  Integrate flymake-proc--start-syntax-check-process
    helper.
    (flymake-proc--start-syntax-check-process): Delete.
    (flymake-proc-stop-all-syntax-checks): Don't use
    flymake-proc--processes, iterate buffers.
    (flymake-proc-compile):
    
    * lisp/progmodes/flymake-ui.el (subr-x): Require it
    explicitly.
    (flymake-diagnostic-functions): Reword docstring.
    (flymake--running-backends, flymake--disabled-backends)
    (flymake--diagnostics-table): Delete.
    (flymake--backend-state): New buffer-local variable.
    (flymake--saving-backend-state, flymake--collect)
    (flymake-running-backends, flymake-disabled-backends)
    (flymake-reporting-backends): New helpers.
    (flymake-is-running): Use flymake-running-backends.
    (flymake--handle-report): Rewrite.
    (flymake-make-report-fn): Ensure REPORT-FN runs in the correct
    buffer or not at all.
    (flymake--disable-backend, flymake--run-backend): Rewrite.
    (flymake-start): Rewrite.
    (flymake-mode): Set flymake--backend-state.
    (flymake--mode-line-format): Rewrite.
    
    * test/lisp/progmodes/flymake-tests.el
    (flymake-tests--wait-for-backends): New helper.
    (flymake-tests--call-with-fixture): Use it.
    (included-c-header-files): Fix whitespace.
    (flymake-tests--diagnose-words): New helper.
    (dummy-backends): Rewrite for new semantics.  Use cl-letf.
    (flymake-tests--assert-set): Use quote.
    (recurrent-backend): New test.
---
 lisp/progmodes/flymake-elisp.el      |  94 ++++---
 lisp/progmodes/flymake-proc.el       | 247 +++++++++---------
 lisp/progmodes/flymake-ui.el         | 476 ++++++++++++++++++++---------------
 test/lisp/progmodes/flymake-tests.el | 248 ++++++++++--------
 4 files changed, 595 insertions(+), 470 deletions(-)

diff --git a/lisp/progmodes/flymake-elisp.el b/lisp/progmodes/flymake-elisp.el
index aee868a..a68302c 100644
--- a/lisp/progmodes/flymake-elisp.el
+++ b/lisp/progmodes/flymake-elisp.el
@@ -48,14 +48,15 @@
 (defun flymake-elisp-checkdoc (report-fn)
   "A flymake backend for `checkdoc'.
 Calls REPORT-FN directly."
-  (when (derived-mode-p 'emacs-lisp-mode)
-    (funcall report-fn
-             (cl-loop for (text start end _unfixable) in
-                      (flymake-elisp--checkdoc-1)
-                      collect
-                      (flymake-make-diagnostic
-                       (current-buffer)
-                       start end :note text)))))
+  (unless (derived-mode-p 'emacs-lisp-mode)
+    (error "Can only work on `emacs-lisp-mode' buffers"))
+  (funcall report-fn
+           (cl-loop for (text start end _unfixable) in
+                    (flymake-elisp--checkdoc-1)
+                    collect
+                    (flymake-make-diagnostic
+                     (current-buffer)
+                     start end :note text))))
 
 (defun flymake-elisp--byte-compile-done (report-fn
                                          origin-buffer
@@ -94,40 +95,59 @@ Calls REPORT-FN directly."
     (kill-buffer output-buffer)
     (ignore-errors (delete-file temp-file))))
 
+(defvar-local flymake-elisp--byte-compile-process nil
+  "Buffer-local process started for byte-compiling the buffer.")
+
 (defun flymake-elisp-byte-compile (report-fn)
-  "A flymake backend for elisp byte compilation.
+  "A Flymake backend for elisp byte compilation.
 Spawn an Emacs process that byte-compiles a file representing the
 current buffer state and calls REPORT-FN when done."
   (interactive (list (lambda (stuff)
                        (message "aha %s" stuff))))
-  (when (derived-mode-p 'emacs-lisp-mode)
-    (let ((temp-file (make-temp-file "flymake-elisp-byte-compile"))
-          (origin-buffer (current-buffer)))
-      (save-restriction
-        (widen)
-        (write-region (point-min) (point-max) temp-file nil 'nomessage))
-      (let* ((output-buffer (generate-new-buffer " 
*flymake-elisp-byte-compile*")))
-        (make-process
-         :name "flymake-elisp-byte-compile"
-         :buffer output-buffer
-         :command (list (expand-file-name invocation-name invocation-directory)
-                        "-Q"
-                        "--batch"
-                        ;; "--eval" "(setq load-prefer-newer t)" ; for testing
-                        "-L" default-directory
-                        "-l" "flymake-elisp"
-                        "-f" "flymake-elisp--batch-byte-compile"
-                        temp-file)
-         :connection-type 'pipe
-         :sentinel
-         (lambda (proc _event)
-           (unless (process-live-p proc)
-             (flymake-elisp--byte-compile-done report-fn
-                                               origin-buffer
-                                               output-buffer
-                                               temp-file))))
-        :stderr null-device
-        :noquery t))))
+  (unless (derived-mode-p 'emacs-lisp-mode)
+    (error "Can only work on `emacs-lisp-mode' buffers"))
+  (when flymake-elisp--byte-compile-process
+    (process-put flymake-elisp--byte-compile-process 'flymake-elisp--obsolete 
t)
+    (when (process-live-p flymake-elisp--byte-compile-process)
+      (kill-process flymake-elisp--byte-compile-process)))
+  (let ((temp-file (make-temp-file "flymake-elisp-byte-compile"))
+        (origin-buffer (current-buffer)))
+    (save-restriction
+      (widen)
+      (write-region (point-min) (point-max) temp-file nil 'nomessage))
+    (let* ((output-buffer (generate-new-buffer " 
*flymake-elisp-byte-compile*")))
+      (setq
+       flymake-elisp--byte-compile-process
+       (make-process
+        :name "flymake-elisp-byte-compile"
+        :buffer output-buffer
+        :command (list (expand-file-name invocation-name invocation-directory)
+                       "-Q"
+                       "--batch"
+                       ;; "--eval" "(setq load-prefer-newer t)" ; for testing
+                       "-L" default-directory
+                       "-l" "flymake-elisp"
+                       "-f" "flymake-elisp--batch-byte-compile"
+                       temp-file)
+        :connection-type 'pipe
+        :sentinel
+        (lambda (proc _event)
+          (unless (process-live-p proc)
+            (unwind-protect
+                (cond
+                 ((zerop (process-exit-status proc))
+                  (flymake-elisp--byte-compile-done report-fn
+                                                    origin-buffer
+                                                    output-buffer
+                                                    temp-file))
+                 ((process-get proc 'flymake-elisp--obsolete)
+                  (flymake-log 3 "proc %s considered obsolete" proc))
+                 (t
+                  (funcall report-fn
+                           :panic
+                           :explanation (format "proc %s died violently" 
proc)))))))))
+      :stderr null-device
+      :noquery t)))
 
 (defun flymake-elisp--batch-byte-compile (&optional file)
   "Helper for `flymake-elisp-byte-compile'.
diff --git a/lisp/progmodes/flymake-proc.el b/lisp/progmodes/flymake-proc.el
index e5e29d2..8dae3a9 100644
--- a/lisp/progmodes/flymake-proc.el
+++ b/lisp/progmodes/flymake-proc.el
@@ -109,12 +109,9 @@ NAME is the file name function to use, default 
`flymake-proc-get-real-file-name'
                               (const :tag "flymake-proc-get-real-file-name" 
nil)
                               function))))
 
-(defvar-local flymake-proc--process nil
+(defvar-local flymake-proc--current-process nil
   "Currently active flymake process for a buffer, if any.")
 
-(defvar flymake-proc--processes nil
-  "List of currently active flymake processes.")
-
 (defvar flymake-proc--report-fn nil
   "If bound, function used to report back to flymake's UI.")
 
@@ -543,9 +540,7 @@ Create parent directories as needed."
   "Parse STRING and collect diagnostics info."
   (flymake-log 3 "received %d byte(s) of output from process %d"
                (length string) (process-id proc))
-  (let ((output-buffer (process-get proc 'flymake-proc--output-buffer))
-        (flymake-proc--report-fn
-         (process-get proc 'flymake-proc--report-fn)))
+  (let ((output-buffer (process-get proc 'flymake-proc--output-buffer)))
     (when (and (buffer-live-p (process-buffer proc))
                output-buffer)
       (with-current-buffer output-buffer
@@ -578,49 +573,55 @@ Create parent directories as needed."
 
 (defun flymake-proc--process-sentinel (proc _event)
   "Sentinel for syntax check buffers."
-  (when (memq (process-status proc) '(signal exit))
-    (let* ((exit-status   (process-exit-status proc))
-           (command       (process-command proc))
-           (source-buffer (process-buffer proc))
-           (flymake-proc--report-fn (process-get proc
-                                                 'flymake-proc--report-fn))
-           (cleanup-f (flymake-proc--get-cleanup-function
-                       (buffer-file-name source-buffer)))
-           (diagnostics (process-get
-                         proc
-                         'flymake-proc--collected-diagnostics))
-           (interrupted (process-get proc 'flymake-proc--interrupted))
-           (panic nil)
-           (output-buffer (process-get proc 'flymake-proc--output-buffer)))
-      (flymake-log 2 "process %d exited with code %d"
-                   (process-id proc) exit-status)
-      (unwind-protect
-          (when (buffer-live-p source-buffer)
-            (flymake-log 3 "cleaning up using %s" cleanup-f)
-            (with-current-buffer source-buffer
-              (funcall cleanup-f)
-              (cond ((equal 0 exit-status)
-                     (funcall flymake-proc--report-fn diagnostics))
-                    (interrupted
-                     (flymake-proc--panic :stopped interrupted))
-                    (diagnostics
-                     ;; non-zero exit but some diagnostics is quite
-                     ;; normal...
-                     (funcall flymake-proc--report-fn diagnostics))
-                    ((null diagnostics)
-                     ;; ...but no diagnostics is strange, so panic.
-                     (setq panic t)
-                     (flymake-proc--panic
-                      :configuration-error
-                      (format "Command %s errored, but no diagnostics"
-                              command))))))
-        (delete-process proc)
-        (setq flymake-proc--processes
-              (delq proc flymake-proc--processes))
-        (if panic
-            (flymake-log 1 "Output buffer %s kept alive for debugging"
-                         output-buffer)
-          (kill-buffer output-buffer))))))
+  (let (debug
+        (pid (process-id proc))
+        (source-buffer (process-buffer proc)))
+    (unwind-protect
+        (when (buffer-live-p source-buffer)
+          (with-current-buffer source-buffer
+            (cond ((process-get proc 'flymake-proc--obsolete)
+                   (flymake-log 3 "proc %s considered obsolete"
+                                pid))
+                  ((process-get proc 'flymake-proc--interrupted)
+                   (flymake-log 3 "proc %s interrupted by user"
+                                pid))
+                  ((not (process-live-p proc))
+                   (let* ((exit-status   (process-exit-status proc))
+                          (command       (process-command proc))
+                          (diagnostics (process-get
+                                        proc
+                                        'flymake-proc--collected-diagnostics)))
+                     (flymake-log 2 "process %d exited with code %d"
+                                  pid exit-status)
+                     (cond
+                      ((equal 0 exit-status)
+                       (funcall flymake-proc--report-fn diagnostics
+                                :explanation (format "a gift from %s" 
(process-id proc))
+                                ))
+                      (diagnostics
+                       ;; non-zero exit but some diagnostics is quite
+                       ;; normal...
+                       (funcall flymake-proc--report-fn diagnostics
+                                :explanation (format "a gift from %s" 
(process-id proc))))
+                      ((null diagnostics)
+                       ;; ...but no diagnostics is strange, so panic.
+                       (setq debug debug-on-error)
+                       (flymake-proc--panic
+                        :configuration-error
+                        (format "Command %s errored, but no diagnostics"
+                                command)))))))))
+      (let ((output-buffer (process-get proc 'flymake-proc--output-buffer)))
+        (cond (debug
+               (flymake-log 3 "Output buffer %s kept alive for debugging"
+                            output-buffer))
+              (t
+               (when (buffer-live-p source-buffer)
+                 (with-current-buffer source-buffer
+                   (let ((cleanup-f (flymake-proc--get-cleanup-function
+                                     (buffer-file-name))))
+                     (flymake-log 3 "cleaning up using %s" cleanup-f)
+                     (funcall cleanup-f))))
+               (kill-buffer output-buffer)))))))
 
 (defun flymake-proc--panic (problem explanation)
   "Tell flymake UI about a fatal PROBLEM with this backend.
@@ -729,88 +730,86 @@ can also be executed interactively independently of
                          diags
                          (append args '(:force t))))
                 t))
-  (cond
-   ((process-live-p flymake-proc--process)
-    (when interactive
-      (user-error
-       "There's already a flymake process running in this buffer")))
-   ((and buffer-file-name
-         ;; Since we write temp files in current dir, there's no point
-         ;; trying if the directory is read-only (bug#8954).
-         (file-writable-p (file-name-directory buffer-file-name))
-         (or (not flymake-proc-compilation-prevents-syntax-check)
-             (not (flymake-proc--compilation-is-running))))
-    (let ((init-f (flymake-proc--get-init-function buffer-file-name)))
-      (unless init-f (error "Can find a suitable init function"))
-      (flymake-proc--clear-buildfile-cache)
-      (flymake-proc--clear-project-include-dirs-cache)
-
-      (let* ((flymake-proc--report-fn report-fn)
-             (cleanup-f (flymake-proc--get-cleanup-function buffer-file-name))
-             (cmd-and-args (funcall init-f))
-             (cmd          (nth 0 cmd-and-args))
-             (args         (nth 1 cmd-and-args))
-             (dir          (nth 2 cmd-and-args)))
-        (cond ((not cmd-and-args)
-               (progn
-                 (flymake-log 0 "init function %s for %s failed, cleaning up"
-                              init-f buffer-file-name)
-                 (funcall cleanup-f)))
-              (t
-               (setq flymake-last-change-time nil)
-               (flymake-proc--start-syntax-check-process cmd
-                                                         args
-                                                         dir)
-               t)))))))
+  (let ((proc flymake-proc--current-process)
+        (flymake-proc--report-fn report-fn))
+    (when (processp proc)
+      (process-put proc 'flymake-proc--obsolete t)
+      (flymake-log 3 "marking %s obsolete" (process-id proc))
+      (when (process-live-p proc)
+        (when interactive
+          (user-error
+           "There's already a flymake process running in this buffer")
+          (kill-process proc))))
+    (when
+        ;; A number of situations make us not want to error right away
+        ;; (and disable ourselves), in case the situation changes in
+        ;; the near future.
+        (and buffer-file-name
+             ;; Since we write temp files in current dir, there's no point
+             ;; trying if the directory is read-only (bug#8954).
+             (file-writable-p (file-name-directory buffer-file-name))
+             (or (not flymake-proc-compilation-prevents-syntax-check)
+                 (not (flymake-proc--compilation-is-running))))
+      (let ((init-f (flymake-proc--get-init-function buffer-file-name)))
+        (unless init-f (error "Can find a suitable init function"))
+        (flymake-proc--clear-buildfile-cache)
+        (flymake-proc--clear-project-include-dirs-cache)
+
+        (let* ((cleanup-f (flymake-proc--get-cleanup-function 
buffer-file-name))
+               (cmd-and-args (funcall init-f))
+               (cmd          (nth 0 cmd-and-args))
+               (args         (nth 1 cmd-and-args))
+               (dir          (nth 2 cmd-and-args))
+               (success nil))
+          (unwind-protect
+              (cond
+               ((not cmd-and-args)
+                (flymake-log 0 "init function %s for %s failed, cleaning up"
+                             init-f buffer-file-name))
+               (t
+                (setq flymake-last-change-time nil)
+                (setq proc
+                      (let ((default-directory (or dir default-directory)))
+                        (when dir
+                          (flymake-log 3 "starting process on dir %s" dir))
+                        (make-process
+                         :name "flymake-proc"
+                         :buffer (current-buffer)
+                         :command (cons cmd args)
+                         :noquery t
+                         :filter
+                         (lambda (proc string)
+                           (let ((flymake-proc--report-fn report-fn))
+                             (flymake-proc--process-filter proc string)))
+                         :sentinel
+                         (lambda (proc event)
+                           (let ((flymake-proc--report-fn report-fn))
+                             (flymake-proc--process-sentinel proc event))))))
+                (process-put proc 'flymake-proc--output-buffer
+                             (generate-new-buffer
+                              (format " *flymake output for %s*" 
(current-buffer))))
+                (setq flymake-proc--current-process proc)
+                (flymake-log 2 "started process %d, command=%s, dir=%s"
+                             (process-id proc) (process-command proc)
+                             default-directory)
+                (setq success t)))
+            (unless success
+              (funcall cleanup-f))))))))
 
 (define-obsolete-function-alias 'flymake-start-syntax-check
   'flymake-proc-legacy-flymake "26.1"
   "Flymake backend based on the original flymake implementation.")
 
-(defun flymake-proc--start-syntax-check-process (cmd args dir)
-  "Start syntax check process."
-  (condition-case-unless-debug err
-      (let* ((process
-              (let ((default-directory (or dir default-directory)))
-                (when dir
-                  (flymake-log 3 "starting process on dir %s" dir))
-                (make-process :name "flymake-proc"
-                              :buffer (current-buffer)
-                              :command (cons cmd args)
-                              :noquery t
-                              :filter 'flymake-proc--process-filter
-                              :sentinel 'flymake-proc--process-sentinel))))
-        (process-put process 'flymake-proc--output-buffer
-                     (generate-new-buffer
-                      (format " *flymake output for %s*" (current-buffer))))
-        (process-put process 'flymake-proc--report-fn
-                     flymake-proc--report-fn)
-
-        (setq-local flymake-proc--process process)
-        (push process flymake-proc--processes)
-
-        (setq flymake-is-running t)
-        (setq flymake-last-change-time nil)
-
-        (flymake-log 2 "started process %d, command=%s, dir=%s"
-                     (process-id process) (process-command process)
-                     default-directory)
-        process)
-    (error
-     (flymake-proc--panic :make-process-error
-                          (format-message
-                           "Failed to launch syntax check process `%s' with 
args %s: %s"
-                           cmd args (error-message-string err)))
-     (funcall (flymake-proc--get-cleanup-function buffer-file-name)))))
-
 (defun flymake-proc-stop-all-syntax-checks (&optional reason)
   "Kill all syntax check processes."
   (interactive (list "Interrupted by user"))
-  (mapc (lambda (proc)
-          (kill-process proc)
-          (process-put proc 'flymake-proc--interrupted reason)
-          (flymake-log 2 "killed process %d" (process-id proc)))
-        flymake-proc--processes))
+  (dolist (buf (buffer-list))
+    (with-current-buffer buf
+      (let (p flymake-proc--current-process)
+        (when (process-live-p p)
+          (kill-process p)
+          (process-put p 'flymake-proc--interrupted reason)
+          (flymake-log 2 "killed process %d" (process-id p)))))))
 
 (defun flymake-proc--compilation-is-running ()
   (and (boundp 'compilation-in-progress)
diff --git a/lisp/progmodes/flymake-ui.el b/lisp/progmodes/flymake-ui.el
index 9d95bf8..fef16e5 100644
--- a/lisp/progmodes/flymake-ui.el
+++ b/lisp/progmodes/flymake-ui.el
@@ -36,7 +36,7 @@
 (require 'thingatpt) ; end-of-thing
 (require 'warnings) ; warning-numeric-level, display-warning
 (require 'compile) ; for some faces
-(eval-when-compile (require 'subr-x)) ; when-let*, if-let*
+(require 'subr-x) ; when-let*, if-let*, hash-table-keys, hash-table-values
 
 (defgroup flymake nil
   "Universal on-the-fly syntax checker."
@@ -168,7 +168,7 @@ are the string substitutions (see the function `format')."
      (format "%s: %s" (buffer-name) msg)
      (if (numberp level)
          (or (nth level
-                  '(:error :warning :debug :debug) )
+                  '(:emergency :error :warning :debug :debug) )
              :error)
        level)
      "*Flymake log*")))
@@ -303,42 +303,39 @@ about where and how to annotate problems diagnosed in a 
buffer.
 
 Whenever Flymake or the user decides to re-check the buffer, each
 function is called with a common calling convention, a single
-REPORT-FN argument, detailed below.  Backend functions are first
-expected to quickly and inexpensively announce the feasibility of
-checking the buffer via the return value (i.e. they aren't
-required to immediately start checking the buffer):
-
-* If the backend function returns nil, Flymake forgets about this
-  backend for the current check, but will call it again for the
-  next one;
-
-* If the backend function returns non-nil, Flymake expects this
-  backend to check the buffer and call its REPORT-FN callback
-  function exactly once.  If the computation involved is
-  inexpensive, the backend function may do so synchronously,
-  before returning.  If it is not, it should do so after
-  returning, using idle timers, asynchronous processes or other
-  asynchronous mechanisms.
-
-* If the backend function signals an error, it is disabled,
-  i.e. Flymake will not use it again for the current or any
-  future checks of this buffer.  Certain commands, like turning
-  `flymake-mode' on and off again, resets the list of disabled
-  backends.
-
-Backends are required to call REPORT-FN with a single argument
-ACTION followed by an optional list of keywords parameters and
+REPORT-FN argument, detailed below.  Backend functions are
+expected to initiate the buffer check, but aren't required to
+complete it check before exiting: if the computation involved is
+expensive, especially for large buffers, that task can be
+scheduled for the future using asynchronous processes or other
+asynchronous mechanisms.
+
+In any case, backend functions are expected to return quickly or
+signal an error, in which case the backend is disabled.  Flymake
+will not try disabled backends again for any future checks of
+this buffer.  Certain commands, like turning `flymake-mode' off
+and on again, reset the list of disabled backends.
+
+If the function returns, Flymake considers the backend to be
+\"running\". If it has not done so already, the backend is
+expected to call the function REPORT-FN with a single argument
+ACTION followed by an optional list of keyword arguments and
 their values (:KEY1 VALUE1 :KEY2 VALUE2...).
 
 The possible values for ACTION are.
 
-* A (possibly empty) list of objects created with
+* A (possibly empty) list of diagnostic objects created with
   `flymake-make-diagnostic', causing Flymake to annotate the
-  buffer with this information and consider the backend has
-  having finished its check normally.
+  buffer with this information.
 
-* The symbol `:progress', signalling that the backend is still
-  working and will call REPORT-FN again in the future.
+  A backend may call REPORT-FN repeatedly in this manner, but
+  only until Flymake considers that the most recently requested
+  buffer check is now obsolete because, say, buffer contents have
+  changed in the meantime. The backend is only given notice of
+  this via a renewed call to the backend function. Thus, to
+  prevent making obsolete reports and wasting resources, backend
+  functions should first cancel any ongoing processing from
+  previous calls.
 
 * The symbol `:panic', signalling that the backend has
   encountered an exceptional situation and should be disabled.
@@ -348,8 +345,8 @@ The recognized optional keyword arguments are:
 * ‘:explanation’: value should give user-readable details of
   the situation encountered, if any.
 
-* ‘:force’: value should be a boolean forcing the Flymake UI
-  to consider the report even if was somehow unexpected.")
+* ‘:force’: value should be a boolean suggesting that the Flymake
+  considers the report even if was somehow unexpected.")
 
 (defvar flymake-diagnostic-types-alist
   `((:error
@@ -495,122 +492,179 @@ return DEFAULT."
 ;; third-party compatibility.
 (define-obsolete-function-alias 'flymake-display-warning 'message-box "26.1")
 
-(defvar-local flymake--running-backends nil
-  "List of currently active flymake backends.
-An active backend is a member of `flymake-diagnostic-functions'
-that has been invoked but hasn't reported any final status yet.")
-
-(defvar-local flymake--disabled-backends nil
-  "List of currently disabled flymake backends.
-A backend is disabled if it reported `:panic'.")
-
-(defvar-local flymake--diagnostics-table nil
-  "Hash table of all diagnostics indexed by backend.")
+(defvar-local flymake--backend-state nil
+  "Buffer-local hash table of a Flymake backend's state.
+The keys to this hash table are functions as found in
+`flymake-diagnostic-functions'. The values are plists where the
+following keys are possible:
+
+`:running', a symbol to keep track of a backend's replies via its
+REPORT-FN argument. A backend is running if this key is
+present. If the key is absent if the backend isn't expecting any
+replies from the backend.
+
+`:diags', a (possibly empty) list of diagnostic objects created
+with `flymake-make-diagnostic'. This key is absent if the
+backend hasn't reported anything yet.
+
+`:disabled', a string with the explanation for a previous
+exceptional situation reported by the backend. If this key is
+present the backend is disabled.")
+
+(defmacro flymake--saving-backend-state (backend state-var &rest body)
+  "Bind BACKEND's STATE-VAR to its state, run BODY, then save it."
+  (declare (indent 2) (debug (sexp sexp &rest form)))
+  (let ((b (make-symbol "b")))
+    `(let* ((,b ,backend)
+            (,state-var (gethash ,b flymake--backend-state)))
+       (unwind-protect
+           (progn ,@body)
+         (puthash ,b ,state-var flymake--backend-state)))))
 
 (defun flymake-is-running ()
   "Tell if flymake has running backends in this buffer"
-  flymake--running-backends)
-
-(defun flymake--disable-backend (backend action &optional explanation)
-  (cl-pushnew backend flymake--disabled-backends)
-  (flymake-log 0 "Disabled the backend %s due to reports of %s (%s)"
-               backend action explanation))
-
-(cl-defun flymake--handle-report (backend action &key explanation force)
-  "Handle reports from flymake backend identified by BACKEND.
-
-BACKEND, ACTION and EXPLANATION conform to the calling convention
-described in `flymake-diagnostic-functions' (which see). Optional
-FORCE says to handle a report even if it was not expected."
-  (cond
-   ((and (not (memq backend flymake--running-backends))
-         (not force))
-    (flymake-error "Ignoring unexpected report from backend %s" backend))
-   ((eq action :progress)
-    (flymake-log 3 "Backend %s reports progress: %s" backend explanation))
-   ((eq :panic action)
-    (flymake--disable-backend backend action explanation))
-   ((listp action)
-    (let ((diagnostics action))
-      (save-restriction
-        (widen)
-        (flymake-delete-own-overlays
-         (lambda (ov)
-           (eq backend
-               (flymake--diag-backend
-                (overlay-get ov 'flymake--diagnostic)))))
-        (puthash backend diagnostics flymake--diagnostics-table)
-        (mapc (lambda (diag)
-                (flymake--highlight-line diag)
-                (setf (flymake--diag-backend diag) backend))
-              diagnostics)
-        (when flymake-check-start-time
-          (flymake-log 2 "backend %s reported %d diagnostics in %.2f second(s)"
-                       backend
-                       (length diagnostics)
-                       (- (float-time) flymake-check-start-time))))))
-   (t
-    (flymake--disable-backend "?"
-                              :strange
-                              (format "unknown action %s (%s)"
-                                      action explanation))))
-  (unless (eq action :progress)
-    (flymake--stop-backend backend)))
-
-(defun flymake-make-report-fn (backend)
+  (flymake-running-backends))
+
+(cl-defun flymake--handle-report (backend token action &key explanation force)
+  "Handle reports from BACKEND identified by TOKEN.
+
+BACKEND, ACTION and EXPLANATION, and FORCE conform to the calling
+convention described in `flymake-diagnostic-functions' (which
+see). Optional FORCE says to handle a report even if TOKEN was
+not expected."
+  (let ((state (gethash backend flymake--backend-state)))
+    (let (expected-token
+          new-diags)
+      (cond
+       ((null state)
+        (flymake-error
+         "Unexpected report from unknown backend %s" backend))
+       ((cl-getf state :disabled)
+        (flymake-error
+         "Unexpected report from disabled backend %s" backend))
+       ((progn
+          (setq expected-token (cl-getf state :running))
+          (null expected-token))
+        ;; should never happen
+        (flymake-error "Unexpected report from stopped backend %s" backend))
+       ((and (not (eq expected-token token))
+             (not force))
+        (flymake-error "Obsolete report from backend %s with explanation %s"
+                       backend explanation))
+       ((eq :panic action)
+        (flymake--disable-backend backend explanation))
+       ((not (listp action))
+        (flymake--disable-backend backend
+                                  (format "Unknown action %S" action))
+        (flymake-error "Expected report, but got unknown key %s" action))
+       (t
+        (setq new-diags action)
+        (save-restriction
+          (widen)
+          (unless (cl-getf state :diags)
+            ;; only delete overlays if this is the first batch of
+            ;; diagnostics we are receiving.
+            (flymake-delete-own-overlays
+             (lambda (ov)
+               (eq backend
+                   (flymake--diag-backend
+                    (overlay-get ov 'flymake--diagnostic))))))
+          (mapc (lambda (diag)
+                  (flymake--highlight-line diag)
+                  (setf (flymake--diag-backend diag) backend))
+                new-diags)
+          (flymake--saving-backend-state backend state
+            (setf (cl-getf state :diags)
+                  (append new-diags (cl-getf state :diags))))
+          (when flymake-check-start-time
+            (flymake-log 3 "backend %s reported %d diagnostics in %.2f 
second(s)"
+                         backend
+                         (length new-diags)
+                         (- (float-time) flymake-check-start-time)))))))))
+
+(defun flymake-make-report-fn (backend &optional token)
   "Make a suitable anonymous report function for BACKEND.
-BACKEND is used to help flymake distinguish diagnostic
-sources."
-  (lambda (&rest args)
-    (apply #'flymake--handle-report backend args)))
-
-(defun flymake--stop-backend (backend)
-  "Stop the backend BACKEND."
-  (setq flymake--running-backends (delq backend flymake--running-backends)))
+BACKEND is used to help flymake distinguish different diagnostic
+sources.  If provided, TOKEN helps flymake distinguish between
+different runs of the same backend."
+  (let ((buffer (current-buffer)))
+    (lambda (&rest args)
+      (when (buffer-live-p buffer)
+        (with-current-buffer buffer
+          (apply #'flymake--handle-report backend token args))))))
+
+(defun flymake--collect (fn)
+  (let (retval)
+    (maphash (lambda (backend state)
+               (when (funcall fn state) (push backend retval)))
+             flymake--backend-state)
+    retval))
+
+(defun flymake-running-backends ()
+  "Compute running Flymake backends in current buffer."
+  (flymake--collect (lambda (state) (cl-getf state :running))))
+
+(defun flymake-disabled-backends ()
+  "Compute disabled Flymake backends in current buffer."
+  (flymake--collect (lambda (state) (cl-getf state :disabled))))
+
+(defun flymake-reporting-backends ()
+  "Compute reporting Flymake backends in current buffer."
+  (flymake--collect (lambda (state) (or (plist-member state :diags)
+                                        (plist-member state :disabled)))))
+
+(defun flymake--disable-backend (backend &optional explanation)
+  "Disable BACKEND because EXPLANATION.
+If is is running also stop it."
+  (flymake-log 2 "Disabling backend %s because %s" backend explanation)
+  (flymake--saving-backend-state backend state
+    (setf (cl-getf state :disabled) explanation)
+    (cl-remf state :running)))
 
 (defun flymake--run-backend (backend)
-  "Run the backend BACKEND."
-  (push backend flymake--running-backends)
-  (remhash backend flymake--diagnostics-table)
-  ;; FIXME: Should use `condition-case-unless-debug' here, but that
-  ;; won't let me catch errors from inside `ert-deftest' where
-  ;; `debug-on-error' is always t
-  (condition-case err
-      (unless (funcall backend
-                       (flymake-make-report-fn backend))
-        (flymake--stop-backend backend))
-    (error
-     (flymake--disable-backend backend :error
-                               err)
-     (flymake--stop-backend backend))))
-
-(defun flymake-start (&optional deferred interactive)
+  "Run the backend BACKEND, reenabling if necessary."
+  (flymake-log 3 "Running backend %s" backend)
+  (let ((run-token (cl-gensym "backend-token")))
+    (flymake--saving-backend-state backend state
+      (setf (cl-getf state :running) run-token)
+      (cl-remf state :disabled)
+      (cl-remf state :diags))
+    ;; FIXME: Should use `condition-case-unless-debug' here, for don't
+    ;; for two reasons: (1) that won't let me catch errors from inside
+    ;; `ert-deftest' where `debug-on-error' appears to be always
+    ;; t. (2) In cases where the user is debugging elisp somewhere
+    ;; else, and using flymake, the presence of a frequently
+    ;; misbehaving backend in the global hook (most likely the legacy
+    ;; backend) will trigger an annoying backtrace.
+    ;;
+    (condition-case err
+        (funcall backend
+                 (flymake-make-report-fn backend run-token))
+      (error
+       (flymake--disable-backend backend err)))))
+
+(defun flymake-start (&optional deferred force)
   "Start a syntax check.
 Start it immediately, or after current command if DEFERRED is
-non-nil.  With optional INTERACTIVE or interactively, clear any
-stale information about running and automatically disabled
-backends."
-  (interactive (list nil t))
+non-nil.  With optional FORCE run even disabled backends.
+
+Interactively, with a prefix arg, FORCE is t."
+  (interactive (list nil current-prefix-arg))
   (cl-labels
       ((start
         ()
         (remove-hook 'post-command-hook #'start 'local)
         (setq flymake-check-start-time (float-time))
-        (when interactive
-          (setq flymake--diagnostics-table (make-hash-table)
-                flymake--running-backends nil
-                flymake--disabled-backends nil))
         (run-hook-wrapped
          'flymake-diagnostic-functions
          (lambda (backend)
-           (cond ((memq backend flymake--running-backends)
-                  (flymake-log 2 "Backend %s still running, not restarting"
-                               backend))
-                 ((memq backend flymake--disabled-backends)
-                  (flymake-log 2 "Backend %s is disabled, not starting"
-                               backend))
-                 (t
-                  (flymake--run-backend backend)))
+           (cond
+            ((and (not force)
+                  (plist-member (gethash backend flymake--backend-state) 
:disabled))
+             (flymake-log 2 "Backend %s is disabled, not starting"
+                          backend))
+            (t
+             (flymake--run-backend backend)))
            nil))))
     (if (and deferred
              this-command)
@@ -636,7 +690,7 @@ backends."
 
       (setq flymake-timer
             (run-at-time nil 1 'flymake-on-timer-event (current-buffer)))
-      (setq flymake--diagnostics-table (make-hash-table))
+      (setq flymake--backend-state (make-hash-table))
 
       (when flymake-start-syntax-check-on-find-file
         (flymake-start)))))
@@ -775,20 +829,24 @@ applied."
 
 (defun flymake--mode-line-format ()
   "Produce a pretty minor mode indicator."
-  (let ((running flymake--running-backends)
-        (reported (cl-plusp
-                   (hash-table-count flymake--diagnostics-table))))
+  (let ((known (hash-table-keys flymake--backend-state))
+        (running (flymake-running-backends))
+        (disabled (flymake-disabled-backends))
+        (reported (flymake-reporting-backends))
+        (diags-by-type (make-hash-table)))
+    (maphash (lambda (_b state)
+               (mapc (lambda (diag)
+                       (push diag
+                             (gethash (flymake--diag-type diag)
+                                      diags-by-type)))
+                     (cl-getf state :diags)))
+             flymake--backend-state)
     `((:propertize " Flymake"
                    mouse-face mode-line-highlight
-                   ,@(when (not reported)
-                       `(face compilation-mode-line-fail))
                    help-echo
-                   ,(concat (format "%s registered backends\n"
-                                    (length flymake-diagnostic-functions))
-                            (format "%s running\n"
-                                    (length running))
-                            (format "%s disabled\n"
-                                    (length flymake--disabled-backends))
+                   ,(concat (format "%s known backends\n" (length known))
+                            (format "%s running\n" (length running))
+                            (format "%s disabled\n" (length disabled))
                             "mouse-1: go to log buffer ")
                    keymap
                    ,(let ((map (make-sparse-keymap)))
@@ -797,69 +855,69 @@ applied."
                           (interactive "e")
                           (switch-to-buffer "*Flymake log*")))
                       map))
-      ,@(when running
-          `(":" (:propertize "Run"
-                             face compilation-mode-line-run
-                             help-echo
-                             ,(format "%s running backends"
-                                      (length running)))))
-      ,@(when reported
-          (let ((by-type (make-hash-table)))
-            (maphash (lambda (_backend diags)
-                       (mapc (lambda (diag)
-                               (push diag
-                                     (gethash (flymake--diag-type diag)
-                                              by-type)))
-                             diags))
-                     flymake--diagnostics-table)
-            (cl-loop
-             for (type . severity)
-             in (cl-sort (mapcar (lambda (type)
-                                   (cons type (flymake--lookup-type-property
-                                               type
-                                               'severity
-                                               (warning-numeric-level 
:error))))
-                                 (cl-union (hash-table-keys by-type)
-                                           '(:error :warning)))
-                         #'>
-                         :key #'cdr)
-             for diags = (gethash type by-type)
-             for face = (flymake--lookup-type-property type
-                                                       'mode-line-face
-                                                       'compilation-error)
-             when (or diags
-                      (>= severity (warning-numeric-level :warning)))
-             collect `(:propertize
-                       ,(format "%d" (length diags))
-                       face ,face
-                       mouse-face mode-line-highlight
-                       keymap
-                       ,(let ((map (make-sparse-keymap))
-                              (type type))
-                          (define-key map [mode-line mouse-4]
-                            (lambda (_event)
-                              (interactive "e")
-                              (flymake-goto-prev-error 1 (list type) t)))
-                          (define-key map [mode-line mouse-5]
-                            (lambda (_event)
-                              (interactive "e")
-                              (flymake-goto-next-error 1 (list type) t)))
-                          map)
-                       help-echo
-                       ,(concat (format "%s diagnostics of type %s\n"
-                                        (propertize (format "%d"
-                                                            (length diags))
-                                                    'face face)
-                                        (propertize (format "%s" type)
-                                                    'face face))
-                                "mouse-4/mouse-5: previous/next of this 
type\n"))
-             into forms
-             finally return
-             `((:propertize "[")
-               ,@(cl-loop for (a . rest) on forms by #'cdr
-                          collect a when rest collect
-                          '(:propertize " "))
-               (:propertize "]"))))))))
+      ,@(if (null reported)
+            (pcase-let ((`(,ind ,face ,explain)
+                         (cond ((null known)
+                                `("?" mode-line "No known backends"))
+                               (running
+                                `("Wait" compilation-mode-line-run
+                                  ,(format "Waiting for %s running backends"
+                                           (length running))))
+                               (disabled
+                                `("!" compilation-mode-line-run
+                                  "All backends disabled")))))
+              `(":"
+                (:propertize ,ind
+                             face ,face
+                             help-echo ,explain)))
+          (cl-loop
+           for (type . severity)
+           in (cl-sort (mapcar (lambda (type)
+                                 (cons type (flymake--lookup-type-property
+                                             type
+                                             'severity
+                                             (warning-numeric-level :error))))
+                               (cl-union (hash-table-keys diags-by-type)
+                                         '(:error :warning)))
+                       #'>
+                       :key #'cdr)
+           for diags = (gethash type diags-by-type)
+           for face = (flymake--lookup-type-property type
+                                                     'mode-line-face
+                                                     'compilation-error)
+           when (or diags
+                    (>= severity (warning-numeric-level :warning)))
+           collect `(:propertize
+                     ,(format "%d" (length diags))
+                     face ,face
+                     mouse-face mode-line-highlight
+                     keymap
+                     ,(let ((map (make-sparse-keymap))
+                            (type type))
+                        (define-key map [mode-line mouse-4]
+                          (lambda (_event)
+                            (interactive "e")
+                            (flymake-goto-prev-error 1 (list type) t)))
+                        (define-key map [mode-line mouse-5]
+                          (lambda (_event)
+                            (interactive "e")
+                            (flymake-goto-next-error 1 (list type) t)))
+                        map)
+                     help-echo
+                     ,(concat (format "%s diagnostics of type %s\n"
+                                      (propertize (format "%d"
+                                                          (length diags))
+                                                  'face face)
+                                      (propertize (format "%s" type)
+                                                  'face face))
+                              "mouse-4/mouse-5: previous/next of this type\n"))
+           into forms
+           finally return
+           `((:propertize "[")
+             ,@(cl-loop for (a . rest) on forms by #'cdr
+                        collect a when rest collect
+                        '(:propertize " "))
+             (:propertize "]")))))))
 
 
 
diff --git a/test/lisp/progmodes/flymake-tests.el 
b/test/lisp/progmodes/flymake-tests.el
index 515aa08..4105d35 100644
--- a/test/lisp/progmodes/flymake-tests.el
+++ b/test/lisp/progmodes/flymake-tests.el
@@ -36,6 +36,26 @@
 
 ;;
 ;;
+(defun flymake-tests--wait-for-backends ()
+  ;; Weirdness here...  http://debbugs.gnu.org/17647#25
+  ;; ... meaning `sleep-for', and even
+  ;; `accept-process-output', won't suffice as ways to get
+  ;; process filters and sentinels to run, though they do work
+  ;; fine in a non-interactive batch session. The only thing
+  ;; that will indeed unblock pending process output is
+  ;; reading an input event, so, as a workaround, use a dummy
+  ;; `read-event' with a very short timeout.
+  (unless noninteractive (read-event "" nil 0.1))
+  (cl-loop repeat 5
+           for notdone = (cl-set-difference (flymake-running-backends)
+                                            (flymake-reporting-backends))
+           while notdone
+           unless noninteractive do (read-event "" nil 0.1)
+           do (sleep-for (+ 0.5 flymake-no-changes-timeout))
+           finally (when notdone (ert-fail
+                                  (format "Some backends not reporting yet %s"
+                                          notdone)))))
+
 (cl-defun flymake-tests--call-with-fixture (fn file
                                                &key (severity-predicate
                                                      nil sev-pred-supplied-p))
@@ -45,8 +65,7 @@ SEVERITY-PREDICATE is used to setup
   (let* ((file (expand-file-name file flymake-tests-data-directory))
          (visiting (find-buffer-visiting file))
          (buffer (or visiting (find-file-noselect file)))
-         (process-environment (cons "LC_ALL=C" process-environment))
-         (i 0))
+         (process-environment (cons "LC_ALL=C" process-environment)))
     (unwind-protect
         (with-current-buffer buffer
           (save-excursion
@@ -54,18 +73,7 @@ SEVERITY-PREDICATE is used to setup
               (setq-local flymake-proc-diagnostic-type-pred 
severity-predicate))
             (goto-char (point-min))
             (unless flymake-mode (flymake-mode 1))
-            ;; Weirdness here...  http://debbugs.gnu.org/17647#25
-            ;; ... meaning `sleep-for', and even
-            ;; `accept-process-output', won't suffice as ways to get
-            ;; process filters and sentinels to run, though they do work
-            ;; fine in a non-interactive batch session. The only thing
-            ;; that will indeed unblock pending process output is
-            ;; reading an input event, so, as a workaround, use a dummy
-            ;; `read-event' with a very short timeout.
-            (unless noninteractive (read-event "" nil 0.1))
-            (while (and (flymake-is-running) (< (setq i (1+ i)) 10))
-              (unless noninteractive (read-event "" nil 0.1))
-              (sleep-for (+ 0.5 flymake-no-changes-timeout)))
+            (flymake-tests--wait-for-backends)
             (funcall fn)))
       (and buffer
            (not visiting)
@@ -118,38 +126,37 @@ SEVERITY-PREDICATE is used to setup
 (ert-deftest different-diagnostic-types ()
   "Test GCC warning via function predicate."
   (skip-unless (and (executable-find "gcc") (executable-find "make")))
-  (flymake-tests--with-flymake
-      ("errors-and-warnings.c")
-    (flymake-goto-next-error)
-    (should (eq 'flymake-error (face-at-point)))
-    (flymake-goto-next-error)
-    (should (eq 'flymake-note (face-at-point)))
-    (flymake-goto-next-error)
-    (should (eq 'flymake-warning (face-at-point)))
-    (flymake-goto-next-error)
-    (should (eq 'flymake-error (face-at-point)))
-    (flymake-goto-next-error)
-    (should (eq 'flymake-warning (face-at-point)))
-    (flymake-goto-next-error)
-    (should (eq 'flymake-warning (face-at-point)))
-    (let ((flymake-wrap-around nil))
-      (should-error (flymake-goto-next-error nil nil t))) ))
+  (let ((flymake-wrap-around nil))
+    (flymake-tests--with-flymake
+        ("errors-and-warnings.c")
+      (flymake-goto-next-error)
+      (should (eq 'flymake-error (face-at-point)))
+      (flymake-goto-next-error)
+      (should (eq 'flymake-note (face-at-point)))
+      (flymake-goto-next-error)
+      (should (eq 'flymake-warning (face-at-point)))
+      (flymake-goto-next-error)
+      (should (eq 'flymake-error (face-at-point)))
+      (flymake-goto-next-error)
+      (should (eq 'flymake-warning (face-at-point)))
+      (flymake-goto-next-error)
+      (should (eq 'flymake-warning (face-at-point)))
+      (should-error (flymake-goto-next-error nil nil t)))))
 
 (ert-deftest included-c-header-files ()
   "Test inclusion of .h header files."
   (skip-unless (and (executable-find "gcc") (executable-find "make")))
-  (flymake-tests--with-flymake
-      ("some-problems.h")
-    (flymake-goto-next-error)
-    (should (eq 'flymake-warning (face-at-point)))
-    (flymake-goto-next-error)
-    (should (eq 'flymake-error (face-at-point)))
-    (let ((flymake-wrap-around nil))
-      (should-error (flymake-goto-next-error nil nil t))) )
-  (flymake-tests--with-flymake
-      ("no-problems.h")
-    (let ((flymake-wrap-around nil))
-      (should-error (flymake-goto-next-error nil nil t))) ))
+  (let ((flymake-wrap-around nil))
+    (flymake-tests--with-flymake
+        ("some-problems.h")
+      (flymake-goto-next-error)
+      (should (eq 'flymake-warning (face-at-point)))
+      (flymake-goto-next-error)
+      (should (eq 'flymake-error (face-at-point)))
+      (should-error (flymake-goto-next-error nil nil t)))
+    (flymake-tests--with-flymake
+        ("no-problems.h")
+      (should-error (flymake-goto-next-error nil nil t)))))
 
 (defmacro flymake-tests--assert-set (set
                                      should
@@ -158,19 +165,15 @@ SEVERITY-PREDICATE is used to setup
   `(progn
      ,@(cl-loop
         for s in should
-        collect `(should (memq ,s ,set)))
+        collect `(should (memq (quote ,s) ,set)))
      ,@(cl-loop
         for s in should-not
-        collect `(should-not (memq ,s ,set)))))
+        collect `(should-not (memq (quote ,s) ,set)))))
 
-(ert-deftest dummy-backends ()
-  "Test GCC warning via function predicate."
-  (with-temp-buffer
-    (cl-labels
-        ((diagnose
-          (report-fn type words)
-          (funcall
-           report-fn
+(defun flymake-tests--diagnose-words
+    (report-fn type words)
+  "Helper. Call REPORT-FN with diagnostics for WORDS in buffer."
+  (funcall report-fn
            (cl-loop
             for word in words
             append
@@ -183,32 +186,34 @@ SEVERITY-PREDICATE is used to setup
                                 (match-end 0)
                                 type
                                 (concat word " is wrong")))))))
-         (error-backend
-          (report-fn)
-          (run-with-timer
-           0.5 nil
-           #'diagnose report-fn :error '("manha" "prognata")))
-         (warning-backend
-          (report-fn)
-          (run-with-timer
-           0.5 nil
-           #'diagnose report-fn :warning '("ut" "dolor")))
-         (sync-backend
-          (report-fn)
-          (diagnose report-fn :note '("quis" "commodo")))
-         (refusing-backend
-          (_report-fn)
-          nil)
-         (panicking-backend
-          (report-fn)
-          (run-with-timer
-           0.5 nil
-           report-fn :panic :explanation "The spanish inquisition!"))
-         (crashing-backend
-          (_report-fn)
-          ;; HACK: Shoosh log during tests
-          (setq-local warning-minimum-log-level :emergency)
-          (error "crashed")))
+
+(ert-deftest dummy-backends ()
+  "Test many different kinds of backends."
+  (with-temp-buffer
+    (cl-letf
+        (((symbol-function 'error-backend)
+          (lambda (report-fn)
+            (run-with-timer
+             0.5 nil
+             #'flymake-tests--diagnose-words report-fn :error '("manha" 
"prognata"))))
+         ((symbol-function 'warning-backend)
+          (lambda (report-fn)
+            (run-with-timer
+             0.5 nil
+             #'flymake-tests--diagnose-words report-fn :warning '("ut" 
"dolor"))))
+         ((symbol-function 'sync-backend)
+          (lambda (report-fn)
+            (flymake-tests--diagnose-words report-fn :note '("quis" 
"commodo"))))
+         ((symbol-function 'panicking-backend)
+          (lambda (report-fn)
+            (run-with-timer
+             0.5 nil
+             report-fn :panic :explanation "The spanish inquisition!")))
+         ((symbol-function 'crashing-backend)
+          (lambda (_report-fn)
+            ;; HACK: Shoosh log during tests
+            (setq-local warning-minimum-log-level :emergency)
+            (error "crashed"))))
       (insert "Lorem ipsum dolor sit amet, consectetur adipiscing
     elit, sed do eiusmod tempor incididunt ut labore et dolore
     manha aliqua. Ut enim ad minim veniam, quis nostrud
@@ -219,31 +224,27 @@ SEVERITY-PREDICATE is used to setup
     sunt in culpa qui officia deserunt mollit anim id est
     laborum.")
       (let ((flymake-diagnostic-functions
-             (list #'error-backend #'warning-backend #'sync-backend
-                   #'refusing-backend #'panicking-backend
-                   #'crashing-backend
-                   )))
+             (list 'error-backend 'warning-backend 'sync-backend
+                   'panicking-backend
+                   'crashing-backend
+                   ))
+            (flymake-wrap-around nil))
         (flymake-mode)
-        ;; FIXME: accessing some flymake-ui's internals here...
-        (flymake-tests--assert-set flymake--running-backends
-          (#'error-backend #'warning-backend #'panicking-backend)
-          (#'sync-backend #'crashing-backend #'refusing-backend))
 
-        (flymake-tests--assert-set flymake--disabled-backends
-          (#'crashing-backend)
-          (#'error-backend #'warning-backend #'sync-backend
-                           #'panicking-backend #'refusing-backend))
+        (flymake-tests--assert-set (flymake-running-backends)
+          (error-backend warning-backend panicking-backend)
+          (crashing-backend))
 
-        (cl-loop repeat 10 while (flymake-is-running)
-                 unless noninteractive do (read-event "" nil 0.1)
-                 do (sleep-for (+ 0.5 flymake-no-changes-timeout)))
+        (flymake-tests--assert-set (flymake-disabled-backends)
+          (crashing-backend)
+          (error-backend warning-backend sync-backend
+                         panicking-backend))
 
-        (should (eq flymake--running-backends '()))
+        (flymake-tests--wait-for-backends)
 
-        (flymake-tests--assert-set flymake--disabled-backends
-          (#'crashing-backend #'panicking-backend)
-          (#'error-backend #'warning-backend #'sync-backend
-                           #'refusing-backend))
+        (flymake-tests--assert-set (flymake-disabled-backends)
+          (crashing-backend panicking-backend)
+          (error-backend warning-backend sync-backend))
 
         (goto-char (point-min))
         (flymake-goto-next-error)
@@ -264,8 +265,55 @@ SEVERITY-PREDICATE is used to setup
         (should (eq 'flymake-warning (face-at-point))) ; dolor
         (flymake-goto-next-error)
         (should (eq 'flymake-error (face-at-point))) ; prognata
-        (let ((flymake-wrap-around nil))
-          (should-error (flymake-goto-next-error nil nil t)))))))
+        (should-error (flymake-goto-next-error nil nil t))))))
+
+(ert-deftest recurrent-backend ()
+  "Test a backend that calls REPORT-FN multiple times"
+  (with-temp-buffer
+    (let (tick)
+      (cl-letf
+          (((symbol-function 'eager-backend)
+            (lambda (report-fn)
+              (funcall report-fn nil :explanation "very eager but no 
diagnostics")
+              (display-buffer (current-buffer))
+              (run-with-timer
+               0.5 nil
+               (lambda ()
+                 (flymake-tests--diagnose-words report-fn :warning 
'("consectetur"))
+                 (setq tick t)
+                 (run-with-timer
+                  0.5 nil
+                  (lambda ()
+                    (flymake-tests--diagnose-words report-fn :error 
'("fugiat"))
+                    (setq tick t))))))))
+        (insert "Lorem ipsum dolor sit amet, consectetur adipiscing
+    elit, sed do eiusmod tempor incididunt ut labore et dolore
+    manha aliqua. Ut enim ad minim veniam, quis nostrud
+    exercitation ullamco laboris nisi ut aliquip ex ea commodo
+    consequat. Duis aute irure dolor in reprehenderit in
+    voluptate velit esse cillum dolore eu fugiat nulla
+    pariatur. Excepteur sint occaecat cupidatat non prognata
+    sunt in culpa qui officia deserunt mollit anim id est
+    laborum.")
+        (let ((flymake-diagnostic-functions
+               (list 'eager-backend))
+              (flymake-wrap-around nil))
+          (flymake-mode)
+          (flymake-tests--assert-set (flymake-running-backends)
+            (eager-backend) ())
+          (cl-loop until tick repeat 4 do (sleep-for 0.2))
+          (setq tick nil)
+          (goto-char (point-max))
+          (flymake-goto-prev-error)
+          (should (eq 'flymake-warning (face-at-point))) ; consectetur
+          (should-error (flymake-goto-prev-error nil nil t))
+          (cl-loop until tick repeat 4 do (sleep-for 0.2))
+          (flymake-goto-next-error)
+          (should (eq 'flymake-error (face-at-point))) ; fugiat
+          (flymake-goto-prev-error)
+          (should (eq 'flymake-warning (face-at-point))) ; back at consectetur
+          (should-error (flymake-goto-prev-error nil nil t))
+          )))))
 
 (provide 'flymake-tests)
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]