diff --git a/.circleci/config.yml b/.circleci/config.yml index ea36a7402..3fbd3084f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,6 +15,18 @@ commands: name: Install unzip command: apt-get update && apt-get install unzip + macos-setup: + steps: + - checkout + - run: + name: Install Emacs latest + command: | + echo "HOMEBREW_NO_AUTO_UPDATE=1" >> $BASH_ENV + brew install homebrew/cask/emacs + - run: + name: Install Eldev + command: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/circle-eldev > x.sh && source ./x.sh + setup-windows: steps: - checkout @@ -74,6 +86,13 @@ jobs: - setup - test + test-macos-emacs-latest: + macos: + xcode: "14.0.0" + steps: + - macos-setup + - test + test-windows-emacs-latest: executor: win/default steps: @@ -100,4 +119,5 @@ workflows: - test-emacs-28 - test-emacs-master - test-lint + - test-macos-emacs-latest - test-windows-emacs-latest diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 000000000..902abb29f --- /dev/null +++ b/.codespellrc @@ -0,0 +1,2 @@ +[codespell] +skip = .git,.eldev,logo diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6dbf5d833..efc548ab5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,26 +3,68 @@ name: CI on: [push, pull_request] jobs: - test: + integration: + # Run integration tests for all OSs and EMACS_VERSIONs. runs-on: ${{matrix.os}} strategy: matrix: - os: [macos-latest] - emacs_version: ['26.3', '27.2', '28.1'] + os: [macos-latest, ubuntu-latest, windows-latest] + emacs_version: ['26.3', '27.2', '28.2'] steps: - name: Set up Emacs + if: "!startsWith (matrix.os, 'windows')" uses: purcell/setup-emacs@master with: version: ${{matrix.emacs_version}} + - name: Set up Emacs on Windows + if: startsWith (matrix.os, 'windows') + uses: jcs090218/setup-emacs-windows@master + with: + version: ${{matrix.emacs_version}} + - name: Install Eldev + if: "!startsWith (matrix.os, 'windows')" run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh + - name: Install Eldev on MS-Windows + if: startsWith (matrix.os, 'windows') + run: | + # Remove expired DST Root CA X3 certificate. Workaround + # for https://debbugs.gnu.org/cgi/bugreport.cgi?bug=51038 + # bug on Emacs 27.2. + gci cert:\LocalMachine\Root\DAC9024F54D8F6DF94935FB1732638CA6AD77C13 + gci cert:\LocalMachine\Root\DAC9024F54D8F6DF94935FB1732638CA6AD77C13 | Remove-Item + + curl.exe -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev.bat | cmd /Q + - name: Check out the source code uses: actions/checkout@v2 - - name: Test the project + - name: Prepare java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + # shadow requires java 11 + java-version: 11 + + - name: Install Clojure Tools + # Use SHA until + # https://github.com/DeLaGuardo/setup-clojure/issues/78 is + # released + uses: DeLaGuardo/setup-clojure@1376ded6747c79645e82c856f16375af5f5de307 + with: + bb: '1.0.165' + cli: '1.10.3.1013' + lein: '2.9.10' + + - uses: actions/setup-node@v3 + with: + node-version: 16 + - run: npm install shadow-cljs@2.20.13 -g + + - name: Test integration run: | - eldev -p -dtT test + eldev -p -dtTC test --test-type integration diff --git a/CHANGELOG.md b/CHANGELOG.md index f54cc1b61..ccb4ce0dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features +- [#3278](https://github.com/clojure-emacs/cider/pull/3278) Introduce integration tests, which also fix a long standing issue with orphaned process on MS-Windows by contracting `taskkill`, if available, to properly kill the nREPL server process tree. - [#3249](https://github.com/clojure-emacs/cider/pull/3249): Add support for Clojure Spec 2. - [#3247](https://github.com/clojure-emacs/cider/pull/3247) Add the `cider-stacktrace-analyze-at-point` and `cider-stacktrace-analyze-in-region` commands to view printed exceptions in the stacktrace inspector. diff --git a/Eldev b/Eldev index d70c01677..a4cbaf261 100644 --- a/Eldev +++ b/Eldev @@ -10,12 +10,36 @@ (eldev-add-loading-roots 'test "test/utils") -;; Otherwise `cider-test.el' will be considered a test file. -(setf eldev-test-fileset "./test/") -;; This file is _supposed_ to be excluded from automated testing. Since Eldev -;; uses everything inside `test/' subdirectory, tell it to leave this file alone -;; explicitly. -(setf eldev-standard-excludes `(:or ,eldev-standard-excludes "test/cider-tests--no-auto.el")) +(defvar cider-test-type 'main) +(setf eldev-standard-excludes `(:or ,eldev-standard-excludes + ;; Avoid including files in test "projects". + (eldev-pcase-exhaustive cider-test-type + (`main "./test/*/") + (`integration '("./test/" "!./test/integration")) + (`all '("./test/*/" "!./test/integration"))) + "test/integration/projects" + ;; This file is _supposed_ to be excluded + ;; from automated testing. + "test/cider-tests--no-auto.el")) + +(eldev-defoption cider-test-selection (type) + "Select tests to run; type can be `main', `integration' or `all'" + :options (-T --test-type) + :for-command test + :value TYPE + :default-value cider-test-type + (unless (memq (intern type) '(main integration all)) + (signal 'eldev-wrong-option-usage `("unknown test type `%s'" ,type))) + (setf cider-test-type (intern type))) + +(add-hook 'eldev-test-hook + (lambda () + (eldev-verbose "Using cider tests of type `%s'" cider-test-type))) +(add-hook 'eldev-executing-command-hook + (lambda (command) + (unless (eq command 'test) + ;; So that e.g. byte-compilation works on all tests. + (setf cider-test-type 'all)))) ;; CIDER cannot be compiled otherwise. (setf eldev-build-load-before-byte-compiling t) diff --git a/cider-connection.el b/cider-connection.el index f7a18d255..a54cb2bbf 100644 --- a/cider-connection.el +++ b/cider-connection.el @@ -180,6 +180,8 @@ buffer." ;; Sync request will hang if the server is dead. (process-live-p (get-buffer-process nrepl-server-buffer)))) (nrepl-sync-request:close repl) + ;; give a chance to the REPL to respond to the closing of the connection + (sleep-for 0.5) (delete-process proc))) (when-let* ((messages-buffer (and nrepl-log-messages (nrepl-messages-buffer repl)))) diff --git a/doc/modules/ROOT/pages/contributing/hacking.adoc b/doc/modules/ROOT/pages/contributing/hacking.adoc index 212a60852..19b8ece46 100644 --- a/doc/modules/ROOT/pages/contributing/hacking.adoc +++ b/doc/modules/ROOT/pages/contributing/hacking.adoc @@ -84,9 +84,20 @@ all the details. If you prefer running all tests outside Emacs that's also an option. -Run all tests with: +There are two test types, `main` (unit tests) and `integration`. By +default only main tests are run. - $ eldev test +Run all unit tests with: + + $ eldev[.bat] test + +Run integration tests with: + + $ eldev[.bat] test --test-type integration + +or all tests with: + + $ eldev[.bat] test --test-type all NOTE: Tests may not run correctly inside Emacs' `shell-mode` buffers. Running them in a terminal is recommended. diff --git a/nrepl-client.el b/nrepl-client.el index 612c2e9a0..e8ac80df8 100644 --- a/nrepl-client.el +++ b/nrepl-client.el @@ -627,12 +627,33 @@ If NO-ERROR is non-nil, show messages instead of throwing an error." ;;; Client: Process Handling (defun nrepl--kill-process (proc) - "Kill PROC using the appropriate, os specific way. -Implement a workaround to clean up an orphaned JVM process left around -after exiting the REPL on some windows machines." - (if (memq system-type '(cygwin windows-nt)) - (interrupt-process proc) - (kill-process proc))) + "Attempt to kill PROC tree. +On MS-Windows, using the standard API is highly likely to leave the child +processes still running in the background as orphans. As a workaround, an +attempt is made to delegate the task to the `taskkill` program, which comes +with windows since at least Windows XP, and fallback to the Emacs API if it +can't be found. + +It is expected that the `(process-status PROC)` return value after PROC is +killed is `exit` when `taskkill` is used and `signal` otherwise." + (cond + ((and (eq system-type 'windows-nt) + (process-live-p proc) + (executable-find "taskkill")) + ;; try to use `taskkill` if available + (with-temp-buffer + (call-process-shell-command (format "taskkill /PID %s /T /F" (process-id proc)) + nil (buffer-name) ) + ;; useful for debugging. + ;;(message ":PROCESS-KILL-OUPUT %s" (buffer-string)) + )) + + ((memq system-type '(cygwin windows-nt)) + ;; fallback, this is considered to work better than `kill-process` on + ;; MS-Windows. + (interrupt-process proc)) + + (t (kill-process proc)))) (defun nrepl-kill-server-buffer (server-buf) "Kill SERVER-BUF and its process." @@ -1090,8 +1111,22 @@ match groups: 1 for the port, and 2 for the host (babashka only).") +(defun cider--process-plist-put (proc prop val) + "Change value in PROC's plist of PROP to VAL. +Value is changed using `plist-put`, of which see." + (thread-first + proc + (process-plist) + (plist-put prop val) + (thread-last (set-process-plist proc)))) + (defun nrepl-server-filter (process output) - "Process nREPL server output from PROCESS contained in OUTPUT." + "Process nREPL server output from PROCESS contained in OUTPUT. + +The PROCESS plist is updated as (non-exhaustive list): + +:cider--nrepl-server-ready set to t when the server is successfully brought +up." ;; In Windows this can be false: (let ((server-buffer (process-buffer process))) (when (buffer-live-p server-buffer) @@ -1117,6 +1152,7 @@ match groups: (setq nrepl-endpoint (list :host host :port port)) (message "[nREPL] server started on %s" port) + (cider--process-plist-put process :cider--nrepl-server-ready t) (when nrepl-on-port-callback (funcall nrepl-on-port-callback (process-buffer process))))))))) @@ -1129,16 +1165,11 @@ match groups: (declare-function cider--close-connection "cider-connection") (defun nrepl-server-sentinel (process event) - "Handle nREPL server PROCESS EVENT." - (let* ((server-buffer (process-buffer process)) - (clients (seq-filter (lambda (b) - (eq (buffer-local-value 'nrepl-server-buffer b) - server-buffer)) - (buffer-list))) - (problem (if (and server-buffer (buffer-live-p server-buffer)) - (with-current-buffer server-buffer - (buffer-substring (point-min) (point-max))) - ""))) + "Handle nREPL server PROCESS EVENT. +On a fatal EVENT, attempt to close any open client connections, and signal +an `error' if the nREPL PROCESS exited because it couldn't start up." + ;; only interested on fatal signals. + (when (not (process-live-p process)) (emacs-bug-46284/when-27.1-windows-nt ;; There is a bug in emacs 27.1 (since fixed) that sets all EVENT ;; descriptions for signals to "unknown signal". We correct this by @@ -1151,17 +1182,22 @@ match groups: (2 (setq event "Interrupt")) ;; SIGKILL==9 emacs nt/inc/ms-w32.h (9 (setq event "Killed"))))) - - (when server-buffer - (kill-buffer server-buffer)) - (cond - ((string-match-p "^killed\\|^interrupt" event) - nil) - ((string-match-p "^hangup" event) - (mapc #'cider--close-connection clients)) - ;; On Windows, a failed start sends the "finished" event. On Linux it sends - ;; "exited abnormally with code 1". - (t (error "Could not start nREPL server: %s" problem))))) + (let* ((server-buffer (process-buffer process)) + (clients (seq-filter (lambda (b) + (eq (buffer-local-value 'nrepl-server-buffer b) + server-buffer)) + (buffer-list)))) + ;; close any known open client connections + (when server-buffer + (kill-buffer server-buffer)) + (mapc #'cider--close-connection clients) + + (if (process-get process :cider--nrepl-server-ready) + (message "nREPL server exited.") + (let ((problem (when (and server-buffer (buffer-live-p server-buffer)) + (with-current-buffer server-buffer + (buffer-substring (point-min) (point-max)))))) + (error "Could not start nREPL server: %s (%S)" problem (string-trim event))))))) ;;; Messages diff --git a/test/cider-tests.el b/test/cider-tests.el index f223ed03b..231ee49e5 100644 --- a/test/cider-tests.el +++ b/test/cider-tests.el @@ -555,11 +555,12 @@ "(do (require '[shadow.cljs.devtools.api :as shadow]) (shadow/browser-repl))"))) (describe "can watch multiple builds" (it "watches 2 builds and selects user-defined builds" - (setq-local cider-shadow-default-options "client-build") - (setq-local cider-shadow-watched-builds '("client-build" "other-build")) - (expect (cider-shadow-cljs-init-form) - :to-equal - "(do (require '[shadow.cljs.devtools.api :as shadow]) (shadow/watch :client-build) (shadow/watch :other-build) (shadow/nrepl-select :client-build))")))) + (with-temp-buffer + (setq-local cider-shadow-default-options "client-build") + (setq-local cider-shadow-watched-builds '("client-build" "other-build")) + (expect (cider-shadow-cljs-init-form) + :to-equal + "(do (require '[shadow.cljs.devtools.api :as shadow]) (shadow/watch :client-build) (shadow/watch :other-build) (shadow/nrepl-select :client-build))"))))) (describe "cider--resolve-project-command" (it "if command starts with ./ it resolves relative to clojure-project-dir" diff --git a/test/integration/integration-test-utils.el b/test/integration/integration-test-utils.el new file mode 100644 index 000000000..a8cc7b081 --- /dev/null +++ b/test/integration/integration-test-utils.el @@ -0,0 +1,114 @@ +;;; integration-test-utils.el -*- lexical-binding: t; -*- + +;; Copyright © 2022 Ioannis Kappas + +;; This file is NOT part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see `http://www.gnu.org/licenses/'. + +;;; Commentary: + +;; Helper utils for use by integration tests. +;; +;; All helper functions should begin with `cider-itu-`. + +;; This file is part of CIDER + +;;; Code: + +(require 'buttercup) +(require 'cider) +(require 'cl-lib) + +(defmacro with-cider-test-sandbox (&rest body) + "Run BODY inside sandbox, with key cider global vars restored on exit. + +Only the following variables are currently restored, please add more as the +test coverage increases: + +`cider-connected-hook`." + (declare (indent 0)) + ;; for dynamic vars, just use a binding under the same name. + `(let ((cider-connected-hook cider-connected-hook)) + ,@body)) + +;; https://emacs.stackexchange.com/a/55031 +(defmacro with-temp-dir (temp-dir &rest body) + "Create a temporary directory and bind it to TEMP-DIR while evaluating BODY. +Remove the temp directory at the end of evaluation." + (declare (indent 1)) + `(let ((,temp-dir (make-temp-file "" t))) + (unwind-protect + (progn + ,@body) + (condition-case err + (delete-directory ,temp-dir t) + (error + (message ":with-temp-dir-error :cannot-remove-temp-dir %S" err)))))) + +(defmacro cider-itu-poll-until (condition timeout-secs) + "Poll every 0.2 secs until CONDITION becomes true or error out if TIMEOUT-SECS elapses." + (let* ((interval-secs 0.2) + (count (truncate (/ timeout-secs interval-secs)))) + `(cl-loop repeat ,count + for condition = ,condition + if condition + return condition + else + do (sleep-for ,interval-secs) + finally (error ":cider-itu-poll-until-errored :timed-out-after-secs %d :waiting-for %S" + ,timeout-secs (quote ,condition))))) + +(defun cider-itu-nrepl-client-connected-ref-make! () + "Returns a gv ref to signal when the client is connected to the nREPL server. +This is done by adding a hook to `cider-connected-hook' and must be run +inside `with-cider-test-sandbox'. + +Use `gv-deref' to deref the value. + +The generalized variable can have the following values + +'!connected the client has not yet connected to the nREPL server. +'connected the client has connected to the nREPL server." + (let ((is-connected '!connected)) + (add-hook 'cider-connected-hook + (lambda () + (setq is-connected 'connected))) + (gv-ref is-connected))) + +(describe "in integration utils test" + (it "that cider-itu-poll-until works when sexpr eventually becomes true." + (let ((stack '(nil 123 456))) + (expect (cider-itu-poll-until (progn (message ":looping... %S" stack) (pop stack)) 1) :to-equal 123) + (expect stack :to-equal '(456))) + (expect (cider-itu-poll-until nil 1) :to-throw 'error)) + + (it "that sand box can restore CIDER global vars." + (let ((count (length cider-connected-hook))) + (with-cider-test-sandbox + (add-hook 'cider-connected-hook (lambda ())) + (expect (length cider-connected-hook) :to-be (1+ count))) + (expect (length cider-connected-hook) :to-be count))) + + (it "that `cider-itu-nrepl-client-connected-ref-make!' return ref changes value when client is connected." + (with-cider-test-sandbox + (let ((is-connected* (cider-itu-nrepl-client-connected-ref-make!))) + (expect (gv-deref is-connected*) :to-equal '!connected) + (run-hooks 'cider-connected-hook) + (expect (gv-deref is-connected*) :to-equal 'connected))))) + + +(provide 'integration-test-utils) + +;;; integration-test-utils.el ends here diff --git a/test/integration/integration-tests.el b/test/integration/integration-tests.el new file mode 100644 index 000000000..2baa6e77f --- /dev/null +++ b/test/integration/integration-tests.el @@ -0,0 +1,237 @@ +;;; integration-tests.el -*- lexical-binding: t; -*- + +;; Copyright © 2022 Ioannis Kappas + +;; This file is NOT part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see `http://www.gnu.org/licenses/'. + +;;; Commentary: + +;; Integration tests + +;; This file is part of CIDER + +;;; Code: + +(require 'buttercup) +(require 'cider) +(require 'nrepl-dict) +(require 'nrepl-tests-utils "test/utils/nrepl-tests-utils") +(require 'integration-test-utils) + +(describe "jack in" + ;; See "babashka" case for commentary on the base template used by all other + ;; tests. + ;; + ;; It has been observed that some REPLs (Clojure cli, shadow) might take a + ;; very long time to bring up/respond/shutdown, and thus sleep duration values + ;; are set rather high. + + (it "to babashka" + (with-cider-test-sandbox + (with-temp-dir temp-dir + ;; set up a project directory in temp + (let* ((project-dir temp-dir) + (bb-edn (expand-file-name "bb.edn" project-dir))) + (write-region "{}" nil bb-edn) + + (with-temp-buffer + ;; set default directory to temp project + (setq-local default-directory project-dir) + + (unwind-protect + (let* (;; Get a gv reference so as to poll if the client has + ;; connected to the nREPL server. + (client-is-connected* (cider-itu-nrepl-client-connected-ref-make!)) + + ;; jack in and get repl buffer + (nrepl-proc (cider-jack-in-clj '())) + (nrepl-buf (process-buffer nrepl-proc))) + + ;; wait until the client has successfully connected to the + ;; nREPL server. + (cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 5) + + ;; give it some time to setup the clj REPL + (cider-itu-poll-until (cider-repls 'clj nil) 5) + + ;; send command to the REPL, and stdout/stderr to + ;; corresponding eval- variables. + (let ((repl-buffer (cider-current-repl)) + (eval-err '()) + (eval-out '())) + (expect repl-buffer :not :to-be nil) + + ;; send command to the REPL + (cider-interactive-eval + ;; ask REPL to return a string that uniquely identifies it. + "(print :bb? (some? (System/getProperty \"babashka.version\")))" + (lambda (return) + (nrepl-dbind-response + return + (out err) + (when err (push err eval-err)) + (when out (push out eval-out)))) ) + + ;; wait for the response to come back. + (cider-itu-poll-until eval-out 5) + + ;; ensure there are no errors and response is as expected. + (expect eval-err :to-equal '()) + (expect eval-out :to-equal '(":bb? true")) + + ;; exit the REPL. + (cider-quit repl-buffer) + + ;; wait for the REPL to exit + (cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5) + (expect (member (process-status nrepl-proc) '(exit signal))))) + + ;; useful for debugging on errors + (when-let ((nrepl-error-buffer (get-buffer "*nrepl-error*"))) + (with-current-buffer nrepl-error-buffer + (message ":*nrepl-error* %S" (substring-no-properties (buffer-string))))))))))) + + (it "to clojure tools cli" + (with-cider-test-sandbox + (with-temp-dir temp-dir + (let* ((project-dir temp-dir) + (deps-edn (expand-file-name "deps.edn" project-dir))) + (write-region "{}" nil deps-edn) + (with-temp-buffer + (setq-local default-directory project-dir) + (unwind-protect + (let* ((client-is-connected* (cider-itu-nrepl-client-connected-ref-make!)) + (nrepl-proc (cider-jack-in-clj `())) + (nrepl-buf (process-buffer nrepl-proc))) + ;; high duration since on windows it takes a long time to startup + (cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 90) + (cider-itu-poll-until (cider-repls 'clj nil) 90) + (let ((repl-buffer (cider-current-repl)) + (eval-err '()) + (eval-out '())) + (expect repl-buffer :not :to-be nil) + (cider-interactive-eval + "(print :clojure? (some? (clojure-version)))" + (lambda (return) + (nrepl-dbind-response + return + (out err) + (when err (push err eval-err)) + (when out (push out eval-out)))) ) + (cider-itu-poll-until eval-out 10) + (expect eval-err :to-equal '()) + (expect eval-out :to-equal '(":clojure? true")) + (cider-quit repl-buffer) + (cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 15) + (expect (member (process-status nrepl-proc) '(exit signal))))) + (when-let ((nrepl-error-buffer (get-buffer "*nrepl-error*"))) + (with-current-buffer nrepl-error-buffer + (message ":*nrepl-error* %S" (substring-no-properties (buffer-string))))))))))) + + (it "to leiningen" + (with-cider-test-sandbox + (with-temp-dir temp-dir + (let* ((project-dir temp-dir) + (project-clj (expand-file-name "project.clj" project-dir))) + (write-region "(defproject cider/integration \"test\" + :dependencies [[org.clojure/clojure \"1.10.3\"]])" + nil project-clj) + (with-temp-buffer + (setq-local default-directory project-dir) + (unwind-protect + (let* ((client-is-connected* (cider-itu-nrepl-client-connected-ref-make!)) + (nrepl-proc (cider-jack-in-clj `())) + (nrepl-buf (process-buffer nrepl-proc))) + (cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 90) + (cider-itu-poll-until (cider-repls 'clj nil) 90) + (let ((repl-buffer (cider-current-repl)) + (eval-err '()) + (eval-out '())) + (expect repl-buffer :not :to-be nil) + (cider-interactive-eval + "(print :clojure? (some? (clojure-version)))" + (lambda (return) + (nrepl-dbind-response + return + (out err) + (when err (push err eval-err)) + (when out (push out eval-out)))) ) + (cider-itu-poll-until eval-out 10) + (expect eval-err :to-equal '()) + (expect eval-out :to-equal '(":clojure? true")) + (cider-quit repl-buffer) + (cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 15) + (expect (member (process-status nrepl-proc) '(exit signal))) + (sleep-for 0.5))) + (when-let ((nrepl-error-buffer (get-buffer "*nrepl-error*"))) + (with-current-buffer nrepl-error-buffer + (message ":*nrepl-error* %S" + (substring-no-properties (buffer-string))))))))))) + + (it "to shadow" + ;; shadow asks user whether they want to open a browser, force to no + (spy-on 'y-or-n-p) + + (with-cider-test-sandbox + (with-temp-dir temp-dir + (let* ((project-dir temp-dir) + (shadow-cljs-edn (expand-file-name "shadow-cljs.edn" project-dir)) + (package-json (expand-file-name "package.json" project-dir))) + (write-region "{}" nil shadow-cljs-edn) + (write-region "{\"dependencies\":{\"shadow-cljs\": \"^2.20.13\"}}" nil package-json) + (let ((default-directory project-dir)) + (message ":npm-install...") + (shell-command "npm install") + (message ":npm-install :done")) + (let ((cider-preferred-build-tool 'shadow-cljs) + ;; request for a node repl, so that shadow forks one. + (cider-shadow-default-options ":node-repl")) + (with-temp-buffer + (setq-local default-directory project-dir) + (unwind-protect + (let* ((client-is-connected* (cider-itu-nrepl-client-connected-ref-make!)) + (nrepl-proc (cider-jack-in-cljs '(:cljs-repl-type shadow))) + (nrepl-buf (process-buffer nrepl-proc))) + (cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 120) + (cider-itu-poll-until (cider-repls 'cljs nil) 120) + (let ((repl-buffer (cider-current-repl)) + (eval-err '()) + (eval-out '())) + (expect repl-buffer :not :to-be nil) + (sleep-for 2) + (cider-interactive-eval + "(print :cljs? (some? *clojurescript-version*))" + (lambda (return) + (nrepl-dbind-response + return + (out err) + (when err (push err eval-err)) + (when out (push out eval-out)))) ) + (cider-itu-poll-until eval-out 10) + (expect eval-err :to-equal '()) + (expect eval-out :to-equal '(":cljs? true\n")) + (cider-quit repl-buffer) + (cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 15) + (expect (member (process-status nrepl-proc) '(exit signal))))) + (when-let ((nrepl-error-buffer (get-buffer "*nrepl-error*"))) + (with-current-buffer nrepl-error-buffer + (message ":*nrepl-error* %S" + (substring-no-properties (buffer-string))))))))))))) + +(provide 'integration-tests) + +;;; integration-tests.el ends here + diff --git a/test/nrepl-client-tests.el b/test/nrepl-client-tests.el index 4f57a24cc..b8da9bccc 100644 --- a/test/nrepl-client-tests.el +++ b/test/nrepl-client-tests.el @@ -159,7 +159,8 @@ ;; server has reported its endpoint (nrepl-tests-sleep-until 2 server-endpoint) (expect server-endpoint :not :to-be nil) - + (expect (plist-get (process-plist server-process) :cider--nrepl-server-ready) + :to-equal t) (condition-case error-details ;; start client process (let* ((client-buffer (get-buffer-create ":nrepl-lifecycle/client")) @@ -182,10 +183,12 @@ (delete-process process-client) ;; server process has been signalled - (nrepl-tests-sleep-until 4 (eq (process-status server-process) - 'signal)) - (expect (process-status server-process) - :to-equal 'signal)) + (nrepl-tests-sleep-until 4 (member (process-status server-process) + '(exit signal))) + (expect (let ((status (process-status server-process))) + (if (eq system-type 'windows-nt) + (eq status 'exit) + (eq status 'signal))))) (error ;; there may be some useful information in the nrepl buffer on error (when-let ((nrepl-error-buffer (get-buffer "*nrepl-error*")))