Skip to content

Commit 068d3a3

Browse files
Merge pull request #27 from johannes-mueller/dape-support
Dape support
2 parents ad7d4f0 + e9db93a commit 068d3a3

5 files changed

+189
-5
lines changed

README.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ suit you best.
118118
Like `test-cockpit-test-or-projectile-test` but does not fallback to
119119
projectile.
120120

121-
122121
You can also use the following commands to run tests in a more manual way
123122

124123
* `test-cockpit-test-project` to run the whole test suite.
@@ -130,6 +129,17 @@ If the current function at point or the current module cannot be determined,
130129
the last tested module resp. last tested function are tested. If there are no
131130
last tests, an error message is thrown.
132131

132+
133+
## Dape support
134+
135+
There are stubs to make use of the [Dape](https://github.com/svaante/dape/)
136+
package to call the recent test run in a Dape debugging session. So far, only
137+
the python backend supports this feature.
138+
139+
You can call this either using the transient UI or by the command
140+
`test-cockpit-dape-debug-repeat-test`.
141+
142+
133143
## Status
134144

135145
The development started more than a year ago in early 2021. Since then I have
@@ -141,7 +151,6 @@ out to work smoothly and to be quite useful.
141151

142152
* Test discovery
143153
* Parsing test results to determine failed tests
144-
* dap-mode integration – launch lastly failed test in dap-mode
145154
* Generalizing it to a more comprehensive build-cockpit also doing simple
146155
builds and things like release uploads.
147156

test-cockpit-python.el

+14
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@
4242
"Implement test-cockpit--engine-current-function-string."
4343
(test-cockpit-python--test-function-path))
4444

45+
(cl-defmethod test-cockpit--engine-dape-last-test-config ((_obj test-cockpit-python-engine))
46+
(let* ((last-cmd (test-cockpit--last-interactive-test-command))
47+
(args (vconcat (pcase last-cmd
48+
('test-cockpit-test-project [])
49+
('test-cockpit-test-module `[,(test-cockpit--last-module-string)])
50+
('test-cockpit-test-function `[,(test-cockpit--last-function-string)]))
51+
(oref (test-cockpit--retrieve-engine) last-args))))
52+
`(command "python"
53+
command-args ("-m" "debugpy.adapter" "--host" "127.0.0.1" "--port" :autoport)
54+
port :autoport :request "launch" :type "python" :module "pytest"
55+
:cwd ,(projectile-project-root)
56+
:args ,args
57+
:justMyCode nil :console "integratedTerminal" :showReturnValue t :stopOnEntry nil)))
58+
4559
(test-cockpit-register-project-type 'python-pip 'test-cockpit-python-engine)
4660
(test-cockpit-register-project-type-alias 'python-pkg 'python-pip)
4761
(test-cockpit-register-project-type-alias 'python-tox 'python-pip)

test-cockpit.el

+31-3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161
;; the last tested module resp. last tested function are tested. If there are no
6262
;; last tests, an error message is thrown.
6363

64+
;; There is experimental state support of the Dape package to run DAP debug sessions.
65+
6466
;;; Code:
6567

6668
(require 'transient)
@@ -137,6 +139,10 @@ the argument list passed to the test frame work."
137139
"Supply the string identifying the current function at point."
138140
nil)
139141

142+
(cl-defmethod test-cockpit--engine-dape-last-test-config ((_obj test-cockpit--engine))
143+
"Supply the dape testing configuration."
144+
nil)
145+
140146
(defun test-cockpit-register-project-type (project-type engine-class)
141147
"Register a language testing package.
142148
PROJECT-TYPE is the type given by `pojectile-project-type' and
@@ -211,6 +217,7 @@ The additional arguments are shipped as ARGS."
211217
(oset engine last-args args)))
212218

213219
(defun test-cockpit--update-last-interactive-command (function)
220+
"Update thte last interactive command function"
214221
(let ((engine (test-cockpit--retrieve-engine)))
215222
(oset engine last-interactive-cmd function)))
216223

@@ -390,7 +397,9 @@ session, the main dispatch dialog is invoked."
390397
(interactive
391398
(list (transient-args 'test-cockpit-prefix)))
392399
(if-let (last-cmd (oref (test-cockpit--real-engine-or-error) last-command))
393-
(test-cockpit--run-test last-cmd)
400+
(if (eq last-cmd 'test-cockpit--last-command-was-dape)
401+
(test-cockpit-dape-debug-repeat-test)
402+
(test-cockpit--run-test last-cmd))
394403
(test-cockpit-dispatch)))
395404

396405
;;;###autoload
@@ -444,7 +453,6 @@ prompt to type a test command is shown."
444453
(test-cockpit--repeat-projectile-test)
445454
(test-cockpit-repeat-test)))
446455

447-
448456
;;;###autoload
449457
(defun test-cockpit--repeat-interactive-test (&optional args)
450458
"Repeat the last interactive test command.
@@ -455,6 +463,19 @@ in order to call the last test action with modified ARGS."
455463
(when-let ((last-cmd (test-cockpit--last-interactive-test-command)))
456464
(funcall last-cmd args)))
457465

466+
;;;###autoload
467+
(defun test-cockpit-dape-debug-repeat-test ()
468+
"Repeat the last test action calling the dape debugger, if available."
469+
(interactive)
470+
(if-let
471+
((config (test-cockpit--dape-debug-last-test)))
472+
(test-cockpit--launch-dape config)
473+
(user-error "No recent test-action has been performed or no Dape support for backend")))
474+
475+
(defun test-cockpit--launch-dape (config)
476+
"Launch the dape debug session and memorize that last test was a dape session."
477+
(dape config)
478+
(oset (test-cockpit--retrieve-engine) last-command 'test-cockpit--last-command-was-dape))
458479

459480
(defun test-cockpit--projectile-build (&optional last-cmd)
460481
"Launch a projectile driven build process.
@@ -522,6 +543,10 @@ repetition."
522543
"Get the last interactive test command."
523544
(oref (test-cockpit--retrieve-engine) last-interactive-cmd))
524545

546+
(defun test-cockpit--dape-debug-last-test ()
547+
"Get the dape configuration for the last test."
548+
(test-cockpit--engine-dape-last-test-config (test-cockpit--retrieve-engine)))
549+
525550
(transient-define-prefix test-cockpit-prefix ()
526551
"Test the project."
527552
:value 'test-cockpit--last-switches
@@ -531,7 +556,8 @@ repetition."
531556
"Setup the main menu common for all projects for testing."
532557
(let ((module-string (or (test-cockpit--current-module-string) (test-cockpit--last-module-string)))
533558
(function-string (or (test-cockpit--current-function-string) (test-cockpit--last-function-string)))
534-
(last-cmd (oref (test-cockpit--real-engine-or-error) last-interactive-cmd)))
559+
(dape-adaptor (test-cockpit--dape-debug-last-test))
560+
(last-cmd (test-cockpit--last-interactive-test-command)))
535561
(vconcat (remove nil (append `("Run tests"
536562
("p" "project" test-cockpit-test-project)
537563
,(if module-string
@@ -542,6 +568,8 @@ repetition."
542568
`("f"
543569
,(format "function: %s" (test-cockpit--strip-project-root function-string))
544570
test-cockpit-test-function))
571+
,(if (and dape-adaptor last-cmd)
572+
`("d" "dape debug repeat" test-cockpit-dape-debug-repeat-test))
545573
("c" "custom" test-cockpit-custom-test-command)
546574
,(if last-cmd
547575
`("r" "repeat" test-cockpit--repeat-interactive-test))))))))

test/test-cockpit.el-test.el

+81
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,39 @@
4242
(should (eq (alist-get 'foo-project-type test-cockpit--project-types)
4343
(alist-get 'foo-project-type-alias test-cockpit--project-types))))
4444

45+
46+
(defclass test-cockpit--dape-engine (test-cockpit--engine)
47+
((current-module-string :initarg :current-module-string
48+
:initform nil)
49+
(current-function-string :initarg :current-function-string
50+
:initform nil)))
51+
52+
(cl-defmethod test-cockpit--test-project-command ((obj test-cockpit--dape-engine))
53+
(lambda (_ args) (concat "test project" " " (string-join args " "))))
54+
(cl-defmethod test-cockpit--test-module-command ((obj test-cockpit--dape-engine))
55+
(lambda (module args) (concat "test module" " " module " " (string-join args " "))))
56+
(cl-defmethod test-cockpit--test-function-command ((obj test-cockpit--dape-engine))
57+
(lambda (func args) (concat "test function" " " func " " (string-join args " "))))
58+
(cl-defmethod test-cockpit--transient-infix ((obj test-cockpit--dape-engine))
59+
["Dape" ("-f" "dape" "--dape")])
60+
(cl-defmethod test-cockpit--engine-current-module-string ((obj test-cockpit--dape-engine))
61+
(oref obj current-module-string))
62+
(cl-defmethod test-cockpit--engine-current-function-string ((obj test-cockpit--dape-engine))
63+
(oref obj current-function-string))
64+
(cl-defmethod test-cockpit--engine-dape-last-test-config ((obj test-cockpit--dape-engine))
65+
'dape-foo-config)
66+
67+
(defun tc--register-dape-project (test-string)
68+
(setq test-cockpit--project-engines nil)
69+
(test-cockpit-register-project-type 'dape-project-type 'test-cockpit--dape-engine)
70+
(mocker-let ((projectile-project-type () ((:output 'dape-project-type :min-occur 0)))
71+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "dape-project" :min-occur 0))))
72+
(oset (test-cockpit--retrieve-engine) current-module-string
73+
(when test-string (concat test-string "-module-string")))
74+
(oset (test-cockpit--retrieve-engine) current-function-string
75+
(when test-string (concat test-string "-function-string")))))
76+
77+
4578
(ert-deftest test-current-module-string-dummy ()
4679
(setq test-cockpit--project-engines nil)
4780
(mocker-let ((projectile-project-type () ((:output 'bar-project-type)))
@@ -361,6 +394,24 @@
361394
(test-cockpit-test-module '("bar" "foo"))
362395
(test-cockpit--do-repeat-function nil)))
363396

397+
(ert-deftest test-dape-debug-repeat-test--not-available ()
398+
(tc--register-foo-project "foo")
399+
(should-error (test-cockpit-dape-debug-repeat-test)))
400+
401+
(ert-deftest test-dape-debug-repeat-test--available ()
402+
(tc--register-dape-project "dape")
403+
(mocker-let ((projectile-project-type () ((:output 'dape-project-type)))
404+
(dape (config) ((:input '(dape-foo-config) :output 'success))))
405+
(test-cockpit-dape-debug-repeat-test)))
406+
407+
408+
(ert-deftest test-dape-debug-repeat-test--repeat ()
409+
(tc--register-dape-project "dape")
410+
(mocker-let ((projectile-project-type () ((:output 'dape-project-type)))
411+
(dape (config) ((:input '(dape-foo-config) :output 'success :occur 2))))
412+
(test-cockpit-dape-debug-repeat-test)
413+
(test-cockpit-repeat-test)))
414+
364415

365416
(ert-deftest test-main-suffix--all-nil ()
366417
(tc--register-foo-project "foo")
@@ -426,6 +477,36 @@
426477
("f" "function: some-last-function" test-cockpit-test-function)
427478
("c" "custom" test-cockpit-custom-test-command)]))))
428479

480+
(ert-deftest test-main-suffix-dape-debug-no-last-test ()
481+
(tc--register-dape-project "dape")
482+
(mocker-let ((projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "dape-project")))
483+
(test-cockpit--current-module-string () ((:output nil)))
484+
(test-cockpit--current-function-string () ((:output nil)))
485+
(test-cockpit--last-module-string () ((:output nil)))
486+
(test-cockpit--last-function-string () ((:output "dape-project/some-last-function")))
487+
(test-cockpit--last-interactive-test-command () ((:output nil))))
488+
(should (equal (test-cockpit--main-suffix)
489+
["Run tests"
490+
("p" "project" test-cockpit-test-project)
491+
("f" "function: some-last-function" test-cockpit-test-function)
492+
("c" "custom" test-cockpit-custom-test-command)]))))
493+
494+
(ert-deftest test-main-suffix-dape-debug-with-last-test ()
495+
(tc--register-dape-project "dape")
496+
(mocker-let ((projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "dape-project")))
497+
(test-cockpit--current-module-string () ((:output nil)))
498+
(test-cockpit--current-function-string () ((:output nil)))
499+
(test-cockpit--last-module-string () ((:output nil)))
500+
(test-cockpit--last-function-string () ((:output "dape-project/some-last-function")))
501+
(test-cockpit--last-interactive-test-command () ((:output 'some-cmd))))
502+
(should (equal (test-cockpit--main-suffix)
503+
["Run tests"
504+
("p" "project" test-cockpit-test-project)
505+
("f" "function: some-last-function" test-cockpit-test-function)
506+
("d" "dape debug repeat" test-cockpit-dape-debug-repeat-test)
507+
("c" "custom" test-cockpit-custom-test-command)
508+
("r" "repeat" test-cockpit--repeat-interactive-test)]))))
509+
429510

430511
(ert-deftest test-repeat-transient-suffix-nil ()
431512
(tc--register-foo-project "foo")

test/test-python.el-test.el

+52
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,58 @@
146146
(expected (pop struct)))
147147
(should (equal (test-cockpit-python--insert-no-coverage-to-switches arglist) expected)))))
148148

149+
(ert-deftest test-python-dape-last-test-project ()
150+
(setq test-cockpit--project-engines nil)
151+
(mocker-let
152+
((projectile-project-type () ((:output 'python-pip :occur 1)))
153+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "foo-project")))
154+
(compile (command) ((:input-matcher (lambda (_) t) :output 'success))))
155+
(test-cockpit-test-project)
156+
(should (equal (test-cockpit--dape-debug-last-test)
157+
'(command "python"
158+
command-args ("-m" "debugpy.adapter" "--host" "127.0.0.1" "--port" :autoport)
159+
port :autoport :request "launch" :type "python" :module "pytest"
160+
:cwd "foo-project"
161+
:args []
162+
:justMyCode nil :console "integratedTerminal" :showReturnValue t :stopOnEntry nil)))))
163+
164+
(ert-deftest test-python-dape-last-test-module ()
165+
(setq test-cockpit--project-engines nil)
166+
(mocker-let
167+
((projectile-project-type () ((:output 'python-pip :occur 1)))
168+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/home/user/project")))
169+
(buffer-file-name () ((:output "/home/user/project/tests/path/to/test_foo.py")))
170+
(compile (command) ((:input-matcher (lambda (_) t) :output 'success))))
171+
(test-cockpit-test-module)
172+
(let ((config (test-cockpit--dape-debug-last-test)))
173+
(should (equal (plist-get config :cwd) "/home/user/project"))
174+
(should (equal (plist-get config :args) ["tests/path/to/test_foo.py"])))))
175+
176+
(ert-deftest test-python-dape-last-test-function-no-switches ()
177+
(setq test-cockpit--project-engines nil)
178+
(mocker-let
179+
((projectile-project-type () ((:output 'python-pip :occur 1)))
180+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/home/user/project")))
181+
(test-cockpit-python--test-function-path () ((:output "test_foo")))
182+
(compile (command) ((:input-matcher (lambda (_) t) :output 'success))))
183+
(test-cockpit-test-function)
184+
(let ((config (test-cockpit--dape-debug-last-test)))
185+
(should (equal (plist-get config :cwd) "/home/user/project"))
186+
(should (equal (plist-get config :args) ["test_foo"])))))
187+
188+
(ert-deftest test-python-dape-last-test-function-switches ()
189+
(setq test-cockpit--project-engines nil)
190+
(mocker-let
191+
((projectile-project-type () ((:output 'python-pip :occur 1)))
192+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/home/user/project")))
193+
(test-cockpit-python--test-function-path () ((:output "test_foo")))
194+
(compile (command) ((:input-matcher (lambda (_) t) :output 'success))))
195+
(test-cockpit-test-function '("--verbose" "--capture=no"))
196+
(let ((config (test-cockpit--dape-debug-last-test)))
197+
(should (equal (plist-get config :cwd) "/home/user/project"))
198+
(should (equal (plist-get config :args) ["test_foo" "--verbose" "--capture=no"])))))
199+
200+
149201
(ert-deftest test-python-find-test-method-simple ()
150202
(let ((buffer-contents "
151203
def test_first_outer():

0 commit comments

Comments
 (0)