diff --git a/lisp/ess-inf.el b/lisp/ess-inf.el index abf6ea80a..de8be961a 100644 --- a/lisp/ess-inf.el +++ b/lisp/ess-inf.el @@ -2918,7 +2918,15 @@ path), in such a way that the host and connection information (if any) in OLD is retained in the NEW path. NEW must be an absolute path, and can be a remote path" (concat (file-remote-p old) - (or (file-remote-p new 'localname) new))) + (ess--path-get-local-portion new))) + +(defun ess--path-get-local-portion (path) + "Obtain the local portion of a (possibly remote) path. +If the string PATH is determined to be a local path, then the +value of PATH is returned unchanged. Otherwise, the portion of +the string in PATH that represents the local portion of the path +is returned." + (or (file-remote-p path 'localname) path)) ;; search path (defun ess--mark-search-list-as-changed () diff --git a/lisp/ess-r-package.el b/lisp/ess-r-package.el index 397ce14e6..8ae3e3be0 100644 --- a/lisp/ess-r-package.el +++ b/lisp/ess-r-package.el @@ -126,12 +126,9 @@ efficiency reasons." (let* ((path (ess-r-package--find-package-path (or dir default-directory))) (name (when path (ess-r-package--find-package-name path))) - (local (if (and path (file-remote-p path)) - (tramp-file-name-localname (tramp-dissect-file-name path)) - path)) (info (if name (list :name name - :root local) + :root path) '(nil)))) ;; If DIR was supplied we cannot cache in the current buffer. (if dir @@ -219,9 +216,11 @@ DIR defaults to the current buffer's file name (if non-nil) or "Set process directory to current package directory." (interactive) (let ((pkg-root (plist-get (ess-r-package-info) :root))) - (if pkg-root - (ess-set-working-directory (abbreviate-file-name pkg-root)) - (user-error "Not in a project")))) + (unless pkg-root + (user-error "Not in a project")) + (let* ((lpath (ess--path-get-local-portion pkg-root)) + (lpath-abbreviated (abbreviate-file-name lpath))) + (ess-set-working-directory lpath-abbreviated)))) ;;;*;;; Evaluation @@ -262,7 +261,8 @@ arguments, or expressions which return R arguments." (ess-project-save-buffers) (message msg (plist-get pkg-info :name)) (display-buffer (ess-get-process-buffer)) - (let ((pkg-path (concat "'" (abbreviate-file-name (plist-get pkg-info :root)) "'"))) + (let* ((lpath (ess--path-get-local-portion (plist-get pkg-info :root))) + (pkg-path (concat "'" (abbreviate-file-name lpath) "'"))) (ess-eval-linewise (format command (concat pkg-path args)))))) (defun ess-r-command--build-args (ix &optional actions) diff --git a/test/ess-test-inf.el b/test/ess-test-inf.el index 7c0763c6e..62d173079 100644 --- a/test/ess-test-inf.el +++ b/test/ess-test-inf.el @@ -394,7 +394,7 @@ some. text (should (cl-every #'string= (ess-get-words-from-vector "c('aaa','bbb\"ccc', 'dddd')\n") '("aaa" "bbb\\\"ccc" "dddd"))))) -(ert-deftest ess--derive-connection-path () +(ert-deftest ess--derive-connection-path-test () (let* ((old-localname "/path/to/file") (new-localname "/home/username/projects") (connection-basic "/ssh:melancholia.danann.net:") @@ -424,6 +424,10 @@ some. text (should (funcall check-new-remotepath "")) (should (funcall check-new-remotepath connection-basic)))) +(ert-deftest ess--path-get-local-portion-test () + (should (string= "/path/to/file" (ess--path-get-local-portion "/path/to/file"))) + (should (string= "/some/file" (ess--path-get-local-portion "/ssh:melancholia.danann.net:/some/file")))) + ;; Test runners ;; Note that we add R-3.2.1 to continuous integration via a symlink to diff --git a/test/ess-test-r-package.el b/test/ess-test-r-package.el index 604c207a2..77ca75c2c 100644 --- a/test/ess-test-r-package.el +++ b/test/ess-test-r-package.el @@ -19,6 +19,7 @@ (require 'ert) (require 'ess-r-mode) (require 'ess-r-package) +(require 'ess-r-flymake) (require 'ess-test-r-utils) ;;; Code: @@ -102,12 +103,150 @@ (should (string= (plist-get pkg-info :name) "foo")) (should (string-match-p "dummy-pkg$" (plist-get pkg-info :root))) (kill-buffer))) + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R") + (let ((pkg-info (ess-r-package-info))) + (should (string= (plist-get pkg-info :name) "foo")) + (should (string-match-p "^/mock:.*dummy-pkg$" (plist-get pkg-info :root))) + (kill-buffer))) (with-ess-test-c-file "dummy-pkg/src/test.c" (let ((pkg-info (ess-r-package-info))) (should (string= (plist-get pkg-info :name) "foo")) (should (string-match-p "dummy-pkg$" (plist-get pkg-info :root))) (kill-buffer))))) +(ert-deftest ess-r-package-project-test () + (with-ess-test-r-file "dummy-pkg/R/test.R" + (let ((project-info (ess-r-package-project))) + (should (equal 'ess-r-package (car project-info))) + (should (string-match-p "dummy-pkg$" (cdr project-info))) + (kill-buffer))) + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R") + (let ((project-info (ess-r-package-project))) + (should (equal 'ess-r-package (car project-info))) + (should (string-match-p "^/mock:.*dummy-pkg$" (cdr project-info))) + (kill-buffer)))) + +(ert-deftest ess-r-package-use-dir-test () + (with-ess-test-r-file "dummy-pkg/R/test.R" + (with-r-running (current-buffer) + (ess-set-working-directory "/") + (ess-wait-for-process) + (should (string= (ess-get-working-directory) "/")) + (ess-r-package-use-dir) + (ess-wait-for-process) + (should (string-match-p "dummy-pkg$" (ess-get-working-directory)))) + (kill-buffer)) + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R") + (with-r-running (current-buffer) + (should (string-match-p "/mock:.*/dummy-pkg/R/test.R" buffer-file-name)) + (ess-set-working-directory "/") + (ess-wait-for-process) + (should (string= (ess-get-working-directory) "/")) + (ess-r-package-use-dir) + (ess-wait-for-process) + (should (string-match-p "dummy-pkg$" (ess-get-working-directory))) + (kill-buffer)))) + +;; Return DIR unless both (i) DIR is the path of package root directory and (ii) +;; the buffer file name is in the tests/ package directory. When both (i) and +;; (ii) hold then return the path corresponding to tests/. +(ert-deftest inferior-ess-r--adjust-startup-directory-test () + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/tests/example.R") + (let* ((pkg-dir (plist-get (ess-r-package-info) :root)) + (inst-dir (expand-file-name "inst" pkg-dir))) ;; arbitrary non-package root directory choice + (should (string-match-p "^/mock:.*/dummy-pkg/tests/$" default-directory)) + (should (string-match-p "^/mock:.*/dummy-pkg$" pkg-dir)) + (should (string= inst-dir + (inferior-ess-r--adjust-startup-directory inst-dir "R"))) + (should (string= default-directory + (inferior-ess-r--adjust-startup-directory pkg-dir "R")))) + (kill-buffer))) + +(ert-deftest ess-r-package-source-dirs-test () + (with-ess-test-r-file "dummy-pkg/R/test.R" + (let ((source-dirs (ess-r-package-source-dirs))) + (should (string-match-p ".*/dummy-pkg/R$" (car source-dirs))) + (should (string-match-p ".*/dummy-pkg/src$" (car (cdr source-dirs)))) + (should (null (cdr (cdr source-dirs)))) + (kill-buffer))) + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R") + (let ((source-dirs (ess-r-package-source-dirs))) + (should (string-match-p "^/mock:.*/dummy-pkg/R$" (car source-dirs))) + (should (string-match-p "^/mock:.*/dummy-pkg/src$" (car (cdr source-dirs)))) + (should (null (cdr (cdr source-dirs)))) + (kill-buffer)))) + +(ert-deftest ess-r-package-save-buffers-test () + ;; modify a file within an R package and try to save it + (with-ess-test-r-file "dummy-pkg/R/test.R" + (let ((new-path (expand-file-name "tmp.R"))) + (write-region "" nil new-path) + (find-file new-path) + (should (string-match-p ".*/dummy-pkg/R/tmp.R$" buffer-file-name)) + (should (not (buffer-modified-p))) + (insert "# buffer update") + (should (buffer-modified-p)) + (let ((ess-save-silently t)) + (ess-project-save-buffers) + (should (string-match-p ".*/dummy-pkg/R/tmp.R$" buffer-file-name)) + (should (not (buffer-modified-p)))) + (kill-buffer "test.R") + (kill-buffer "tmp.R") + (delete-file new-path))) + ;; modify a file within an R package with a remote path and try to save it + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R") + (let ((new-path (expand-file-name "tmp.R"))) + (write-region "" nil new-path) + (find-file new-path) + (should (string-match-p "^/mock:.*/dummy-pkg/R/tmp.R$" buffer-file-name)) + (should (not (buffer-modified-p))) + (insert "# buffer update") + (should (buffer-modified-p)) + (let ((ess-save-silently t)) + (ess-project-save-buffers) + (should (string-match-p "^/mock:.*/dummy-pkg/R/tmp.R$" buffer-file-name)) + (should (not (buffer-modified-p)))) + (kill-buffer "test.R") + (kill-buffer "tmp.R") + (delete-file new-path)))) + +(ert-deftest ess-r-package-eval-linewise-test () + (let ((output-regex "^# '.*/dummy-pkg'$")) + ;; Test with an R package on a local filesystem + (with-ess-test-r-file "dummy-pkg/R/test.R" + (with-r-running (current-buffer) + (let ((actual (output (ess-r-package-eval-linewise "# %s")))) + (should (string-match-p output-regex actual)))) + (kill-buffer)) + ;; Test with an R package on a remote filesystem. The remote prefix portion + ;; of the package location should be stripped from the command. + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R") + (with-r-running (current-buffer) + (should (string-match-p "^/mock:.*/dummy-pkg/R/test.R$" buffer-file-name)) + (let ((actual (output (ess-r-package-eval-linewise "# %s")))) + (should (string-match-p output-regex actual)) + (should (not (string-match-p "/mock:" actual))))) + (kill-buffer)))) + +(ert-deftest ess-r--flymake-parse-output-test () + (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R") + (let ((ess-proj-file (expand-file-name "../.lintr")) + (cur-dir-file (expand-file-name ".lintr"))) + ;; no .lintr file + (should (null (ess-r--find-lintr-file))) + ;; .lintr file in the package directory + (write-region "" nil ess-proj-file) + (let ((actual (ess-r--find-lintr-file))) + (should (string-match-p "^/mock:.*/dummy-pkg/\\.lintr$" actual))) + ;; .lintr file in the current directory takes precedence over any other + ;; locations + (write-region "" nil cur-dir-file) + (let ((actual (ess-r--find-lintr-file))) + (should (string-match-p "^/mock:.*/dummy-pkg/R/\\.lintr$" actual))) + ;; clean up created files + (delete-file ess-proj-file) + (delete-file cur-dir-file)))) + (provide 'ess-test-r-package) ;;; ess-test-r-package.el ends here diff --git a/test/ess-test-r-utils.el b/test/ess-test-r-utils.el index b6229389a..48177faa4 100644 --- a/test/ess-test-r-utils.el +++ b/test/ess-test-r-utils.el @@ -20,6 +20,7 @@ (require 'ert) (require 'etest) (require 'ess-r-mode) +(require 'tramp) (defvar ess-test-fixtures-directory (expand-file-name "fixtures" @@ -304,6 +305,25 @@ representative to the common interactive use with tracebug on." (eval . (ess-test-r-set-local-process))) ,@body))) +;; Define a mock TRAMP method to use for testing. This code is taken from +;; `tramp-tests.el'. +(add-to-list + 'tramp-methods + '("mock" + (tramp-login-program "sh") + (tramp-login-args (("-i"))) + (tramp-direct-async-args (("-c"))) + (tramp-remote-shell "/bin/sh") + (tramp-remote-shell-args ("-c")) + (tramp-connection-timeout 10))) + +(defun ess-test-create-remote-path (path) + "Construct a remote path using the 'mock' TRAMP method. +Take a string PATH representing a local path, and construct a +remote path that uses the 'mock' TRAMP method." + (let ((full-path (abbreviate-file-name (expand-file-name path)))) + (concat "/mock::" full-path))) + (provide 'ess-test-r-utils) ;;; ess-test-r-utils.el ends here