103 lines
3.5 KiB
EmacsLisp
103 lines
3.5 KiB
EmacsLisp
;;; +django-tests.el -*- lexical-binding: t; -*-
|
|
|
|
(require 'projectile)
|
|
(require 'python)
|
|
(require 'subr-x)
|
|
|
|
(defun django--project-root ()
|
|
(or (projectile-project-root)
|
|
(user-error "Not in a Projectile project")))
|
|
|
|
(defun django--module-from-file (file root)
|
|
(let* ((rel (file-relative-name file root))
|
|
(no-ext (file-name-sans-extension rel)))
|
|
(replace-regexp-in-string "/" "." no-ext)))
|
|
|
|
(defun django--def-name-at-point ()
|
|
(save-excursion
|
|
(when (ignore-errors (python-nav-beginning-of-defun) t)
|
|
(when (looking-at (rx bol (* space) "def" (+ space)
|
|
(group (+ (or word ?_))) (* space) "("))
|
|
(match-string-no-properties 1)))))
|
|
|
|
(defun django--enclosing-class-name ()
|
|
(save-excursion
|
|
(let ((found nil)
|
|
(limit (point-min)))
|
|
(while (and (not found) (> (point) limit))
|
|
(python-nav-beginning-of-statement)
|
|
(when (looking-at (rx bol (* space) "class" (+ space)
|
|
(group (+ (or word ?_)))))
|
|
(setq found (match-string-no-properties 1)))
|
|
(condition-case _err
|
|
(python-nav-backward-block)
|
|
(error (goto-char limit))))
|
|
found)))
|
|
|
|
(defun django--selector-at-point ()
|
|
(let ((fn (django--def-name-at-point))
|
|
(cls (django--enclosing-class-name)))
|
|
(cond
|
|
((and fn (string-prefix-p "test_" fn))
|
|
(if cls (format "%s.%s" cls fn) fn))
|
|
(cls cls)
|
|
(t nil))))
|
|
|
|
(defun django--ensure-env (root)
|
|
(let ((default-directory root))
|
|
(when (fboundp 'envrc-reload)
|
|
(envrc-reload))))
|
|
|
|
(defface django-test-command-face
|
|
'((t :weight bold :foreground "orange" :height 1.15))
|
|
"Face used for Django test commands.")
|
|
|
|
(defun django--insert-command-header (buf root command)
|
|
"Insert a loud command header at the top of BUF."
|
|
(with-current-buffer (get-buffer-create buf)
|
|
(let ((inhibit-read-only t))
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(insert (propertize (make-string 72 ?═) 'face 'shadow) "\n")
|
|
(insert (propertize
|
|
(format "$ (cd %s && %s)\n" root command)
|
|
'face 'django-test-command-face))
|
|
(insert (propertize (make-string 72 ?═) 'face 'shadow) "\n\n")))))
|
|
|
|
(defun django-run-tests (&optional module selector)
|
|
(interactive)
|
|
(let* ((root (django--project-root))
|
|
(module-part (or module ""))
|
|
(selector-part (if selector (concat "." selector) ""))
|
|
(param (string-trim (concat module-part selector-part)))
|
|
(command (format "python manage.py test --keepdb %s" param))
|
|
(buf "*Django tests*"))
|
|
;; Ensure envrc/direnv is current
|
|
(let ((default-directory root))
|
|
(when (fboundp 'envrc-reload) (envrc-reload)))
|
|
|
|
;; Start the process (this may clear/initialize the buffer)
|
|
(projectile-run-async-shell-command-in-root command buf)
|
|
|
|
;; Now insert header at top (won't get wiped)
|
|
(django--insert-command-header buf root command)
|
|
|
|
(pop-to-buffer buf)))
|
|
|
|
(defun django-run-test-at-point ()
|
|
(interactive)
|
|
(let* ((root (django--project-root))
|
|
(file (or (buffer-file-name) (user-error "Buffer is not visiting a file")))
|
|
(module (django--module-from-file file root))
|
|
(sel (django--selector-at-point)))
|
|
(django-run-tests module sel)))
|
|
|
|
(defun django-run-tests-for-current-file ()
|
|
(interactive)
|
|
(let* ((root (django--project-root))
|
|
(file (or (buffer-file-name) (user-error "Buffer is not visiting a file")))
|
|
(module (django--module-from-file file root)))
|
|
(django-run-tests module nil)))
|
|
|
|
(provide '+django-tests)
|