commit | author | age
|
5cb5f7
|
1 |
;;; flycheck-ert.el --- Flycheck: ERT extensions -*- lexical-binding: t; -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2017-2018 Flycheck contributors |
|
4 |
;; Copyright (C) 2013-2016 Sebastian Wiesner and Flycheck contributors |
|
5 |
|
|
6 |
;; Author: Sebastian Wiesner <swiesner@lunaryorn.com> |
|
7 |
;; Maintainer: Clément Pit-Claudel <clement.pitclaudel@live.com> |
|
8 |
;; fmdkdd <fmdkdd@gmail.com> |
|
9 |
;; URL: https://github.com/flycheck/flycheck |
|
10 |
|
|
11 |
;; This file is not part of GNU Emacs. |
|
12 |
|
|
13 |
;; This program is free software; you can redistribute it and/or modify |
|
14 |
;; it under the terms of the GNU General Public License as published by |
|
15 |
;; the Free Software Foundation, either version 3 of the License, or |
|
16 |
;; (at your option) any later version. |
|
17 |
|
|
18 |
;; This program is distributed in the hope that it will be useful, |
|
19 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
20 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
21 |
;; GNU General Public License for more details. |
|
22 |
|
|
23 |
;; You should have received a copy of the GNU General Public License |
|
24 |
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
25 |
|
|
26 |
;;; Commentary: |
|
27 |
|
|
28 |
;; Unit testing library for Flycheck, the modern on-the-fly syntax checking |
|
29 |
;; extension for GNU Emacs. |
|
30 |
|
|
31 |
;; Provide various utility functions and unit test helpers to test Flycheck and |
|
32 |
;; Flycheck extensions. |
|
33 |
|
|
34 |
;;; Code: |
|
35 |
|
|
36 |
(require 'flycheck) |
|
37 |
(require 'ert) |
|
38 |
(require 'macroexp) ; For macro utilities |
|
39 |
|
|
40 |
|
|
41 |
;;; Compatibility |
|
42 |
|
|
43 |
(eval-and-compile |
|
44 |
;; Provide `ert-skip' and friends for Emacs 24.3 |
|
45 |
(defconst flycheck-ert-ert-can-skip (fboundp 'ert-skip) |
|
46 |
"Whether ERT supports test skipping.") |
|
47 |
|
|
48 |
(unless (fboundp 'define-error) |
|
49 |
;; from Emacs `subr.el' |
|
50 |
(defun define-error (name message &optional parent) |
|
51 |
"Define NAME as a new error signal. |
|
52 |
MESSAGE is a string that will be output to the echo area if such an error |
|
53 |
is signaled without being caught by a `condition-case'. |
|
54 |
PARENT is either a signal or a list of signals from which it inherits. |
|
55 |
Defaults to `error'." |
|
56 |
(unless parent (setq parent 'error)) |
|
57 |
(let ((conditions |
|
58 |
(if (consp parent) |
|
59 |
(apply #'append |
|
60 |
(mapcar |
|
61 |
(lambda (parent) |
|
62 |
(cons parent |
|
63 |
(or (get parent 'error-conditions) |
|
64 |
(error "Unknown signal `%s'" parent)))) |
|
65 |
parent)) |
|
66 |
(cons parent (get parent 'error-conditions))))) |
|
67 |
(put name 'error-conditions |
|
68 |
(delete-dups (copy-sequence (cons name conditions)))) |
|
69 |
(when message (put name 'error-message message))))) |
|
70 |
|
|
71 |
(unless flycheck-ert-ert-can-skip |
|
72 |
;; Fake skipping |
|
73 |
|
|
74 |
(define-error 'flycheck-ert-skipped "Test skipped") |
|
75 |
|
|
76 |
(defun ert-skip (data) |
|
77 |
(signal 'flycheck-ert-skipped data)) |
|
78 |
|
|
79 |
(defmacro skip-unless (form) |
|
80 |
`(unless (ignore-errors ,form) |
|
81 |
(signal 'flycheck-ert-skipped ',form))) |
|
82 |
|
|
83 |
(defun ert-test-skipped-p (result) |
|
84 |
(and (ert-test-failed-p result) |
|
85 |
(eq (car (ert-test-failed-condition result)) |
|
86 |
'flycheck-ert-skipped))))) |
|
87 |
|
|
88 |
|
|
89 |
;;; Internal variables |
|
90 |
|
|
91 |
(defvar flycheck-ert--resource-directory nil |
|
92 |
"The directory to get resources from in this test suite.") |
|
93 |
|
|
94 |
|
|
95 |
;;; Resource management macros |
|
96 |
|
|
97 |
(defmacro flycheck-ert-with-temp-buffer (&rest body) |
|
98 |
"Eval BODY within a temporary buffer. |
|
99 |
|
|
100 |
Like `with-temp-buffer', but resets the modification state of the |
|
101 |
temporary buffer to make sure that it is properly killed even if |
|
102 |
it has a backing file and is modified." |
|
103 |
(declare (indent 0)) |
|
104 |
`(with-temp-buffer |
|
105 |
(unwind-protect |
|
106 |
,(macroexp-progn body) |
|
107 |
;; Reset modification state of the buffer, and unlink it from its backing |
|
108 |
;; file, if any, because Emacs refuses to kill modified buffers with |
|
109 |
;; backing files, even if they are temporary. |
|
110 |
(set-buffer-modified-p nil) |
|
111 |
(set-visited-file-name nil 'no-query)))) |
|
112 |
|
|
113 |
(defmacro flycheck-ert-with-file-buffer (file-name &rest body) |
|
114 |
"Create a buffer from FILE-NAME and eval BODY. |
|
115 |
|
|
116 |
BODY is evaluated with `current-buffer' being a buffer with the |
|
117 |
contents FILE-NAME." |
|
118 |
(declare (indent 1)) |
|
119 |
`(let ((file-name ,file-name)) |
|
120 |
(unless (file-exists-p file-name) |
|
121 |
(error "%s does not exist" file-name)) |
|
122 |
(flycheck-ert-with-temp-buffer |
|
123 |
(insert-file-contents file-name 'visit) |
|
124 |
(set-visited-file-name file-name 'no-query) |
|
125 |
(cd (file-name-directory file-name)) |
|
126 |
;; Mark the buffer as not modified, because we just loaded the file up to |
|
127 |
;; now. |
|
128 |
(set-buffer-modified-p nil) |
|
129 |
,@body))) |
|
130 |
|
|
131 |
(defmacro flycheck-ert-with-help-buffer (&rest body) |
|
132 |
"Execute BODY and kill the help buffer afterwards. |
|
133 |
|
|
134 |
Use this macro to test functions that create a Help buffer." |
|
135 |
(declare (indent 0)) |
|
136 |
`(unwind-protect |
|
137 |
,(macroexp-progn body) |
|
138 |
(when (buffer-live-p (get-buffer (help-buffer))) |
|
139 |
(kill-buffer (help-buffer))))) |
|
140 |
|
|
141 |
(defmacro flycheck-ert-with-global-mode (&rest body) |
|
142 |
"Execute BODY with Global Flycheck Mode enabled. |
|
143 |
|
|
144 |
After BODY, restore the old state of Global Flycheck Mode." |
|
145 |
(declare (indent 0)) |
|
146 |
`(let ((old-state global-flycheck-mode)) |
|
147 |
(unwind-protect |
|
148 |
(progn |
|
149 |
(global-flycheck-mode 1) |
|
150 |
,@body) |
|
151 |
(global-flycheck-mode (if old-state 1 -1))))) |
|
152 |
|
|
153 |
(defmacro flycheck-ert-with-env (env &rest body) |
|
154 |
"Add ENV to `process-environment' in BODY. |
|
155 |
|
|
156 |
Execute BODY with a `process-environment' which contains all |
|
157 |
variables from ENV added. |
|
158 |
|
|
159 |
ENV is an alist, where each cons cell `(VAR . VALUE)' is a |
|
160 |
environment variable VAR to be added to `process-environment' |
|
161 |
with VALUE." |
|
162 |
(declare (indent 1)) |
|
163 |
`(let ((process-environment (copy-sequence process-environment))) |
|
164 |
(pcase-dolist (`(,var . ,value) ,env) |
|
165 |
(setenv var value)) |
|
166 |
,@body)) |
|
167 |
|
|
168 |
|
|
169 |
;;; Test resources |
|
170 |
(defun flycheck-ert-resource-filename (resource-file) |
|
171 |
"Determine the absolute file name of a RESOURCE-FILE. |
|
172 |
|
|
173 |
Relative file names are expanded against |
|
174 |
`flycheck-ert--resource-directory'." |
|
175 |
(expand-file-name resource-file flycheck-ert--resource-directory)) |
|
176 |
|
|
177 |
(defmacro flycheck-ert-with-resource-buffer (resource-file &rest body) |
|
178 |
"Create a temp buffer from a RESOURCE-FILE and execute BODY. |
|
179 |
|
|
180 |
The absolute file name of RESOURCE-FILE is determined with |
|
181 |
`flycheck-ert-resource-filename'." |
|
182 |
(declare (indent 1)) |
|
183 |
`(flycheck-ert-with-file-buffer |
|
184 |
(flycheck-ert-resource-filename ,resource-file) |
|
185 |
,@body)) |
|
186 |
|
|
187 |
|
|
188 |
;;; Test suite initialization |
|
189 |
|
|
190 |
(defun flycheck-ert-initialize (resource-dir) |
|
191 |
"Initialize a test suite with RESOURCE-DIR. |
|
192 |
|
|
193 |
RESOURCE-DIR is the directory, `flycheck-ert-resource-filename' |
|
194 |
should use to lookup resource files." |
|
195 |
(when flycheck-ert--resource-directory |
|
196 |
(error "Test suite already initialized")) |
|
197 |
(let ((tests (ert-select-tests t t))) |
|
198 |
;; Select all tests |
|
199 |
(unless tests |
|
200 |
(error "No tests defined. \ |
|
201 |
Call `flycheck-ert-initialize' after defining all tests!")) |
|
202 |
|
|
203 |
(setq flycheck-ert--resource-directory resource-dir) |
|
204 |
|
|
205 |
;; Emacs 24.3 don't support skipped tests, so we add poor man's test |
|
206 |
;; skipping: We mark skipped tests as expected failures by adjusting the |
|
207 |
;; expected result of all test cases. Not particularly pretty, but works :) |
|
208 |
(unless flycheck-ert-ert-can-skip |
|
209 |
(dolist (test tests) |
|
210 |
(let ((result (ert-test-expected-result-type test))) |
|
211 |
(setf (ert-test-expected-result-type test) |
|
212 |
`(or ,result (satisfies ert-test-skipped-p)))))))) |
|
213 |
|
|
214 |
|
|
215 |
;;; Test case definitions |
|
216 |
(defmacro flycheck-ert-def-checker-test (checker language name |
|
217 |
&rest keys-and-body) |
|
218 |
"Define a test case for a syntax CHECKER for LANGUAGE. |
|
219 |
|
|
220 |
CHECKER is a symbol or a list of symbols denoting syntax checkers |
|
221 |
being tested by the test. The test case is skipped, if any of |
|
222 |
these checkers cannot be used. LANGUAGE is a symbol or a list of |
|
223 |
symbols denoting the programming languages supported by the |
|
224 |
syntax checkers. This is currently only used for tagging the |
|
225 |
test appropriately. |
|
226 |
|
|
227 |
NAME is a symbol denoting the local name of the test. The test |
|
228 |
itself is ultimately named |
|
229 |
`flycheck-define-checker/CHECKER/NAME'. If CHECKER is a list, |
|
230 |
the first checker in the list is used for naming the test. |
|
231 |
|
|
232 |
Optionally, the keyword arguments `:tags' and `:expected-result' |
|
233 |
may be given. They have the same meaning as in `ert-deftest.', |
|
234 |
and are added to the tags and result expectations set up by this |
|
235 |
macro. |
|
236 |
|
|
237 |
The remaining forms KEYS-AND-BODY denote the body of the test |
|
238 |
case, including assertions and setup code." |
|
239 |
(declare (indent 3)) |
|
240 |
(unless checker |
|
241 |
(error "No syntax checkers specified")) |
|
242 |
(unless language |
|
243 |
(error "No languages specified")) |
|
244 |
(let* ((checkers (if (symbolp checker) (list checker) checker)) |
|
245 |
(checker (car checkers)) |
|
246 |
(languages (if (symbolp language) (list language) language)) |
|
247 |
(language-tags (mapcar (lambda (l) (intern (format "language-%s" l))) |
|
248 |
languages)) |
|
249 |
(checker-tags (mapcar (lambda (c) (intern (format "checker-%s" c))) |
|
250 |
checkers)) |
|
251 |
(local-name (or name 'default)) |
|
252 |
(full-name (intern (format "flycheck-define-checker/%s/%s" |
|
253 |
checker local-name))) |
|
254 |
(keys-and-body (ert--parse-keys-and-body keys-and-body)) |
|
255 |
(body (cadr keys-and-body)) |
|
256 |
(keys (car keys-and-body)) |
|
257 |
(default-tags '(syntax-checker external-tool))) |
|
258 |
`(ert-deftest ,full-name () |
|
259 |
:expected-result ,(or (plist-get keys :expected-result) :passed) |
|
260 |
:tags (append ',(append default-tags language-tags checker-tags) |
|
261 |
,(plist-get keys :tags)) |
|
262 |
,@(mapcar (lambda (c) |
|
263 |
`(skip-unless |
|
264 |
;; Ignore non-command checkers |
|
265 |
(or (not (flycheck-checker-get ',c 'command)) |
|
266 |
(executable-find (flycheck-checker-executable ',c))))) |
|
267 |
checkers) |
|
268 |
,@body))) |
|
269 |
|
|
270 |
|
|
271 |
;;; Test case results |
|
272 |
|
|
273 |
(defun flycheck-ert-syntax-check-timed-out-p (result) |
|
274 |
"Whether RESULT denotes a timed-out test. |
|
275 |
|
|
276 |
RESULT is an ERT test result object." |
|
277 |
(and (ert-test-failed-p result) |
|
278 |
(eq (car (ert-test-failed-condition result)) |
|
279 |
'flycheck-ert-syntax-check-timed-out))) |
|
280 |
|
|
281 |
|
|
282 |
;;; Syntax checking in tests |
|
283 |
|
|
284 |
(defvar-local flycheck-ert-syntax-checker-finished nil |
|
285 |
"Non-nil if the current checker has finished.") |
|
286 |
|
|
287 |
(add-hook 'flycheck-after-syntax-check-hook |
|
288 |
(lambda () (setq flycheck-ert-syntax-checker-finished t))) |
|
289 |
|
|
290 |
(defconst flycheck-ert-checker-wait-time 10 |
|
291 |
"Time to wait until a checker is finished in seconds. |
|
292 |
|
|
293 |
After this time has elapsed, the checker is considered to have |
|
294 |
failed, and the test aborted with failure.") |
|
295 |
|
|
296 |
(define-error 'flycheck-ert-syntax-check-timed-out "Syntax check timed out.") |
|
297 |
|
|
298 |
(defun flycheck-ert-wait-for-syntax-checker () |
|
299 |
"Wait until the syntax check in the current buffer is finished." |
|
300 |
(let ((starttime (float-time))) |
|
301 |
(while (and (not flycheck-ert-syntax-checker-finished) |
|
302 |
(< (- (float-time) starttime) flycheck-ert-checker-wait-time)) |
|
303 |
(sleep-for 1)) |
|
304 |
(unless (< (- (float-time) starttime) flycheck-ert-checker-wait-time) |
|
305 |
(flycheck-stop) |
|
306 |
(signal 'flycheck-ert-syntax-check-timed-out nil))) |
|
307 |
(setq flycheck-ert-syntax-checker-finished nil)) |
|
308 |
|
|
309 |
(defun flycheck-ert-buffer-sync () |
|
310 |
"Like `flycheck-buffer', but synchronously." |
|
311 |
(setq flycheck-ert-syntax-checker-finished nil) |
|
312 |
(should (not (flycheck-running-p))) |
|
313 |
(flycheck-mode) ; This will only start a deferred check, |
|
314 |
(flycheck-buffer) ; so we need an explicit manual check |
|
315 |
;; After starting the check, the checker should either be running now, or |
|
316 |
;; already be finished (if it was fast). |
|
317 |
(should (or flycheck-current-syntax-check |
|
318 |
flycheck-ert-syntax-checker-finished)) |
|
319 |
;; Also there should be no deferred check pending anymore |
|
320 |
(should-not (flycheck-deferred-check-p)) |
|
321 |
(flycheck-ert-wait-for-syntax-checker)) |
|
322 |
|
|
323 |
(defun flycheck-ert-ensure-clear () |
|
324 |
"Clear the current buffer. |
|
325 |
|
|
326 |
Raise an assertion error if the buffer is not clear afterwards." |
|
327 |
(flycheck-clear) |
|
328 |
(should (not flycheck-current-errors)) |
|
329 |
(should (not (-any? (lambda (ov) (overlay-get ov 'flycheck-overlay)) |
|
330 |
(overlays-in (point-min) (point-max)))))) |
|
331 |
|
|
332 |
|
|
333 |
;;; Test assertions |
|
334 |
|
|
335 |
(defun flycheck-error-without-group (err) |
|
336 |
"Return a copy ERR with the `group' property set to nil." |
|
337 |
(let ((copy (copy-flycheck-error err))) |
|
338 |
(setf (flycheck-error-group copy) nil) |
|
339 |
copy)) |
|
340 |
|
|
341 |
(defun flycheck-ert-should-overlay (error) |
|
342 |
"Test that ERROR has a proper overlay in the current buffer. |
|
343 |
|
|
344 |
ERROR is a Flycheck error object." |
|
345 |
(let* ((overlay (-first (lambda (ov) |
|
346 |
(equal (flycheck-error-without-group |
|
347 |
(overlay-get ov 'flycheck-error)) |
|
348 |
(flycheck-error-without-group error))) |
|
349 |
(flycheck-overlays-in 0 (+ 1 (buffer-size))))) |
|
350 |
(region |
|
351 |
;; Overlays of errors from other files are on the first line |
|
352 |
(if (flycheck-relevant-error-other-file-p error) |
|
353 |
(cons (point-min) |
|
354 |
(save-excursion (goto-char (point-min)) (point-at-eol))) |
|
355 |
(flycheck-error-region-for-mode error 'symbols))) |
|
356 |
(level (flycheck-error-level error)) |
|
357 |
(category (flycheck-error-level-overlay-category level)) |
|
358 |
(face (get category 'face)) |
|
359 |
(fringe-bitmap (flycheck-error-level-fringe-bitmap level)) |
|
360 |
(fringe-face (flycheck-error-level-fringe-face level)) |
|
361 |
(fringe-icon (list 'left-fringe fringe-bitmap fringe-face))) |
|
362 |
(should overlay) |
|
363 |
(should (overlay-get overlay 'flycheck-overlay)) |
|
364 |
(should (= (overlay-start overlay) (car region))) |
|
365 |
(should (= (overlay-end overlay) (cdr region))) |
|
366 |
(should (eq (overlay-get overlay 'face) face)) |
|
367 |
(should (equal (get-char-property 0 'display |
|
368 |
(overlay-get overlay 'before-string)) |
|
369 |
fringe-icon)) |
|
370 |
(should (eq (overlay-get overlay 'category) category)) |
|
371 |
(should (equal (flycheck-error-without-group (overlay-get overlay |
|
372 |
'flycheck-error)) |
|
373 |
(flycheck-error-without-group error))))) |
|
374 |
|
|
375 |
(defun flycheck-ert-should-errors (&rest errors) |
|
376 |
"Test that the current buffers has ERRORS. |
|
377 |
|
|
378 |
ERRORS is a list of errors expected to be present in the current |
|
379 |
buffer. Each error is given as a list of arguments to |
|
380 |
`flycheck-error-new-at'. |
|
381 |
|
|
382 |
If ERRORS are omitted, test that there are no errors at all in |
|
383 |
the current buffer. |
|
384 |
|
|
385 |
With ERRORS, test that each error in ERRORS is present in the |
|
386 |
current buffer, and that the number of errors in the current |
|
387 |
buffer is equal to the number of given ERRORS. In other words, |
|
388 |
check that the buffer has all ERRORS, and no other errors." |
|
389 |
(let ((expected (mapcar (apply-partially #'apply #'flycheck-error-new-at) |
|
390 |
errors))) |
|
391 |
(should (equal (mapcar #'flycheck-error-without-group expected) |
|
392 |
(mapcar #'flycheck-error-without-group |
|
393 |
flycheck-current-errors))) |
|
394 |
;; Check that related errors are the same |
|
395 |
(cl-mapcar (lambda (err1 err2) |
|
396 |
(should (equal (mapcar #'flycheck-error-without-group |
|
397 |
(flycheck-related-errors err1 expected)) |
|
398 |
(mapcar #'flycheck-error-without-group |
|
399 |
(flycheck-related-errors err2))))) |
|
400 |
expected flycheck-current-errors) |
|
401 |
(mapc #'flycheck-ert-should-overlay expected)) |
|
402 |
(should (= (length errors) |
|
403 |
(length (flycheck-overlays-in (point-min) (point-max)))))) |
|
404 |
|
|
405 |
(define-error 'flycheck-ert-suspicious-checker "Suspicious state from checker") |
|
406 |
|
|
407 |
(defun flycheck-ert-should-syntax-check (resource-file modes &rest errors) |
|
408 |
"Test a syntax check in RESOURCE-FILE with MODES. |
|
409 |
|
|
410 |
RESOURCE-FILE is the file to check. MODES is a single major mode |
|
411 |
symbol or a list thereof, specifying the major modes to syntax |
|
412 |
check with. If more than one major mode is specified, the test |
|
413 |
is run for each mode separately, so if you give three major |
|
414 |
modes, the entire test will run three times. ERRORS is the list |
|
415 |
of expected errors, as in `flycheck-ert-should-errors'. If |
|
416 |
omitted, the syntax check must not emit any errors. The errors |
|
417 |
are cleared after each test. |
|
418 |
|
|
419 |
The syntax checker is selected via standard syntax checker |
|
420 |
selection. To test a specific checker, you need to set |
|
421 |
`flycheck-checker' or `flycheck-disabled-checkers' accordingly |
|
422 |
before using this predicate, depending on whether you want to use |
|
423 |
manual or automatic checker selection. |
|
424 |
|
|
425 |
During the syntax check, configuration files of syntax checkers |
|
426 |
are also searched in the `config-files' sub-directory of the |
|
427 |
resource directory." |
|
428 |
(when (symbolp modes) |
|
429 |
(setq modes (list modes))) |
|
430 |
(dolist (mode modes) |
|
431 |
(unless (fboundp mode) |
|
432 |
(ert-skip (format "%S missing" mode))) |
|
433 |
(flycheck-ert-with-resource-buffer resource-file |
|
434 |
(funcall mode) |
|
435 |
;; Load safe file-local variables because some tests depend on them |
|
436 |
(let ((enable-local-variables :safe) |
|
437 |
;; Disable all hooks at this place, to prevent 3rd party packages |
|
438 |
;; from interfering |
|
439 |
(hack-local-variables-hook)) |
|
440 |
(hack-local-variables)) |
|
441 |
;; Configure config file locating for unit tests |
|
442 |
(let ((process-hook-called 0)) |
|
443 |
(add-hook 'flycheck-process-error-functions |
|
444 |
(lambda (_err) |
|
445 |
(setq process-hook-called (1+ process-hook-called)) |
|
446 |
nil) |
|
447 |
nil :local) |
|
448 |
(add-hook 'flycheck-status-changed-functions |
|
449 |
(lambda (status) |
|
450 |
(when (eq status 'suspicious) |
|
451 |
(signal 'flycheck-ert-suspicious-checker nil)))) |
|
452 |
(flycheck-ert-buffer-sync) |
|
453 |
(apply #'flycheck-ert-should-errors errors) |
|
454 |
(should (= process-hook-called (length errors)))) |
|
455 |
(flycheck-ert-ensure-clear)))) |
|
456 |
|
|
457 |
(defun flycheck-ert-at-nth-error (n) |
|
458 |
"Determine whether point is at the N'th Flycheck error. |
|
459 |
|
|
460 |
Return non-nil if the point is at the N'th Flycheck error in the |
|
461 |
current buffer. Otherwise return nil." |
|
462 |
(let* ((error (nth (1- n) flycheck-current-errors)) |
|
463 |
(mode flycheck-highlighting-mode) |
|
464 |
(region (flycheck-error-region-for-mode error mode))) |
|
465 |
(and (member error (flycheck-overlay-errors-at (point))) |
|
466 |
(= (point) (car region))))) |
|
467 |
|
|
468 |
(defun flycheck-ert-explain--at-nth-error (n) |
|
469 |
"Explain a failed at-nth-error predicate at N." |
|
470 |
(let ((errors (flycheck-overlay-errors-at (point)))) |
|
471 |
(if (null errors) |
|
472 |
(format "Expected to be at error %s, but no error at point %s" |
|
473 |
n (point)) |
|
474 |
(let ((pos (cl-position (car errors) flycheck-current-errors))) |
|
475 |
(format "Expected to be at error %s, but point %s is at error %s" |
|
476 |
n (point) (1+ pos)))))) |
|
477 |
|
|
478 |
(put 'flycheck-ert-at-nth-error 'ert-explainer |
|
479 |
'flycheck-ert-explain--at-nth-error) |
|
480 |
|
|
481 |
(provide 'flycheck-ert) |
|
482 |
|
|
483 |
;;; flycheck-ert.el ends here |