Browse Source

New commands to connect Indium

* indium-interaction.el (indium-connect, indium-launch): New commands used for
  all backends using the .indium.json project file.
workspaces
Nicolas Petton 2 years ago
parent
commit
3e7f181dca
No known key found for this signature in database GPG Key ID: E8BCD7866AFCF978
10 changed files with 399 additions and 323 deletions
  1. +50
    -44
      indium-chrome.el
  2. +25
    -2
      indium-interaction.el
  3. +81
    -69
      indium-nodejs.el
  4. +5
    -10
      indium-v8.el
  5. +124
    -122
      indium-workspace.el
  6. +9
    -0
      test/fixtures/.indium.json
  7. +29
    -21
      test/integration/indium-nodejs-integration-test.el
  8. +20
    -7
      test/test-helper.el
  9. +55
    -47
      test/unit/indium-breakpoint-test.el
  10. +1
    -1
      test/unit/indium-nodejs-test.el

+ 50
- 44
indium-chrome.el View File

@ -21,12 +21,9 @@
;;; Commentary:
;; Handle indium connections to Chrom{e|ium} using the v8 backend.
;;
;; To open a Indium connection, enable chromium/chrome remote debugging:
;;
;; chromium --remote-debugging-port=9222 https://gnu.org
;;; Code:
(require 'url)
(require 'json)
@ -54,31 +51,57 @@
"Chrome executable."
:type '(file))
(defcustom indium-chrome-port
(defcustom indium-chrome-default-port
9222
"Chrome remote debugger port."
"Default Chrome remote debugger port."
:type '(integer))
(defcustom indium-chrome-default-host
"localhost"
"Default Chrome remote debugger host."
:type '(string))
(defvar indium-chrome-url-history nil
"Chrome urls history.")
;;;###autoload
(defun indium-run-chrome (url)
"Start chrome/chromium with remote debugging enabled.
Open URL if provided."
(interactive (list (completing-read "Url: "
nil
nil
nil
nil
'indium-chrome-url-history
(car indium-chrome-url-history))))
(defun indium-connect-to-chrome ()
"Open a connection to a Chrome tab."
(when (or (null indium-current-connection)
(yes-or-no-p "This requires closing the current connection. Are you sure? "))
(when-indium-connected
(indium-quit))
(let* ((host (indium-chrome--host))
(port (indium-chrome--port)))
(indium-chrome--get-tabs-data host port #'indium-chrome--connect-to-tab))))
(defun indium-launch-chrome ()
"Start chrome/chromium with remote debugging enabled."
(make-process :name "indium-chrome-process"
:command (list (indium-chrome--find-executable)
(format "--remote-debugging-port=%s" indium-chrome-port)
(or url "")))
(format "--remote-debugging-port=%s"
(indium-chrome--port))
(indium-chrome--url)))
(message "Connecting to Chrome instance...")
(indium-chrome--try-connect "127.0.0.1" 10))
(indium-chrome--try-connect 10))
(defun indium-chrome--port ()
"Return the debugging port for the Chrome process.
The port is either read from the workpace configuration file or
`indium-chrome-default-port'."
(map-elt indium-workspace-configuration 'port indium-chrome-default-port))
(defun indium-chrome--host ()
"Return the debugging host for the Chrome process.
The host is either read from the workpace configuration file or
`indium-chrome-default-host'."
(map-elt indium-workspace-configuration 'host indium-chrome-default-host))
(defun indium-chrome--url ()
"Return the url to open for the Chrome process."
(let ((url (map-elt indium-workspace-configuration 'url)))
(unless url
(user-error "No Chrome url specified in the .indium.json file"))
url))
(defun indium-chrome--find-executable ()
"Find chrome executable using `indium-chrome-executable'."
@ -87,31 +110,18 @@ Open URL if provided."
(user-error "Cannot find chrome/chromium binary (%s) in PATH" indium-chrome-executable))
executable))
(defun indium-chrome--try-connect (host num-tries)
"Try to connect to chrome on HOST.
(defun indium-chrome--try-connect (num-tries)
"Try to connect to chrome.
Try a maximum of NUM-TRIES."
(message "Trying to connect to the Chrome instance...")
(sleep-for 1)
(indium-chrome--get-tabs-data host
indium-chrome-port
(indium-chrome--get-tabs-data (indium-chrome--host)
(indium-chrome--port)
(lambda (tabs)
(if tabs
(indium-chrome--connect-to-tab tabs)
(when (> num-tries 0)
(indium-chrome--try-connect host (1- num-tries)))))))
;;;###autoload
(defun indium-connect-to-chrome ()
"Open a connection to a v8 tab."
(interactive)
(when (or (null indium-current-connection)
(yes-or-no-p "This requires closing the current connection. Are you sure? "))
(when-indium-connected
(indium-quit))
(let ((host (read-from-minibuffer "Host: " "127.0.0.1"))
(port (read-from-minibuffer "Port: " (number-to-string indium-chrome-port))))
(indium-chrome--get-tabs-data host port #'indium-chrome--connect-to-tab))))
(indium-chrome--try-connect (1- num-tries)))))))
(defun indium-chrome--get-tabs-data (host port callback)
"Get the list of open tabs on HOST:PORT and evaluate CALLBACK with it."
@ -139,12 +149,8 @@ If there are more then one tab available ask the user which tab to connect."
(let* ((tab (seq-find (lambda (tab)
(string= (map-elt tab 'url) url))
tabs))
(websocket-url (map-elt tab 'webSocketDebuggerUrl))
(workspace))
;; No need to setup a workspace when using the file protocol.
(unless (string= (url-type (url-generic-parse-url url)) "file")
(setq workspace (indium-workspace-read)))
(indium-v8--open-ws-connection url websocket-url nil nil workspace)))
(websocket-url (map-elt tab 'webSocketDebuggerUrl)))
(indium-v8--open-ws-connection url websocket-url nil nil)))
(defun indium-chrome--read-tab-data ()
"Return the JSON tabs data in the current buffer."


+ 25
- 2
indium-interaction.el View File

@ -21,10 +21,10 @@
;;; Commentary:
;; Minor mode for interacting with a JavaScript runtime. This mode provides
;; commands for managing breakpoints and evaluating code.
;; commands connecting to a runtime, managing breakpoints and evaluating code.
;;; Code:
(require 'js2-mode)
(require 'map)
(require 'seq)
@ -32,6 +32,7 @@
(require 'xref)
(require 'easymenu)
(require 'indium-workspace)
(require 'indium-backend)
(require 'indium-inspector)
(require 'indium-breakpoint)
@ -44,6 +45,28 @@
(defvar indium-update-script-source-hook nil
"Hook run when script source is updated.")
;;;###autoload
(defun indium-connect ()
"Open a new connection to a runtime."
(interactive)
(with-indium-workspace-configuration
(pcase (map-elt indium-workspace-configuration 'type)
("node" (indium-connect-to-nodejs))
("chrome" (indium-connect-to-chrome))
(_ (user-error "Invalid project type, check the .indium.json project file")))))
;;;###autoload
(defun indium-launch ()
"Start a process (web browser or NodeJS) and attempt to connect to it."
(interactive)
(with-indium-workspace-configuration
(pcase (map-elt indium-workspace-configuration 'type)
("node" (indium-launch-node))
("chrome" (indium-launch-chrome))
(_ (user-error "Invalid project type, check the .indium.json project file")))))
(defun indium-quit ()
"Close the current connection and kill its REPL buffer if any.
If a process is attached to the connection, kill it as well.


+ 81
- 69
indium-nodejs.el View File

@ -21,22 +21,12 @@
;;; Commentary:
;; Handle indium connections to a NodeJS process using the v8 backend.
;; The nodejs process must be started with the `--inspect' flag:
;;
;; node --inspect myfile.js
;; node --inspect=localhost:9876 myfile.js
;;
;; To break on the first line of the application code, provide the --debug-brk
;; flag in addition to --inspect.
;;
;; You can optionally use `indium-run-node' to start a node process, in which
;; case the `--inspect' and `--debug-brk' flags will be added automatically.
;;
;; Important note: For this package to work, NodeJS version 7.0 (or any newer
;; version) is required.
;;; Code:
(require 'url)
(require 'url-parse)
(require 'json)
@ -45,6 +35,7 @@
(require 'subr-x)
(require 'indium-v8)
(require 'indium-workspace)
(declare-function indium-repl-get-buffer "indium-repl.el")
@ -53,77 +44,97 @@
:prefix "indium-nodejs-"
:group 'indium)
(defcustom indium-nodejs-inspect-brk t
(defcustom indium-nodejs-default-inspect-brk t
"When non-nil, break the execution at the first statement."
:type 'boolean)
(defvar indium-nodejs-commands-history nil
"Nodejs commands history.")
;;;###autoload
(defun indium-run-node (command)
(defcustom indium-nodejs-default-host
"localhost"
"Default NodeJS remote debugger host."
:type '(string))
(defcustom indium-nodejs-default-port
9229
"Default NodeJS remote debugger port."
:type '(integer))
(defun indium-nodejs--command ()
"Return the command to be run to start a node process.
The command is read from the workspace configuration file."
(let ((command (map-elt indium-workspace-configuration 'command)))
(unless command
(user-error "No NodeJS command specified in the .indium.json file"))
command))
(defun indium-launch-node ()
"Start a NodeJS process.
Execute COMMAND, adding the `--inspect' flag. When the process
is ready, open an Indium connection on it.
Execute the command based on `indium-nodejs--command', adding the
`--inspect' flag. When the process is ready, open an Indium
connection on it.
If `indium-nodejs-inspect-brk' is set to non-nil, break the
If `indium-nodejs--inspect-brk' is set to non-nil, break the
execution at the first statement.
If a connection is already open, close it."
(interactive (list (read-shell-command "Node command: "
(or (car indium-nodejs-commands-history) "node ")
'indium-nodejs-commands-history)))
(indium-maybe-quit)
(unless indium-current-connection
(let ((process (make-process :name "indium-nodejs-process"
(let* ((default-directory (indium-workspace-root))
(process (make-process :name "indium-nodejs-process"
:buffer "*node process*"
:filter #'indium-nodejs--process-filter
:command (list shell-file-name
shell-command-switch
(indium-nodejs--add-flags command)))))
(indium-nodejs--command-with-flags)))))
(switch-to-buffer (process-buffer process)))))
;;;###autoload
(defun indium-restart-node ()
"Restart the current nodejs process, and connect to it.
If no process has been started, or if it was not started using
`indium-run-node', do nothing."
(interactive)
(if-let ((connection indium-current-connection)
(command (car indium-nodejs-commands-history))
;; Don't do anything when not a nodejs connection
(nodejs (map-elt (indium-current-connection-props) 'nodejs))
(process (indium-current-connection-process)))
;; Make sure we are in the same directory as the current connection.
;; TODO: set the directory in the connection directly instead
;; of relying on the REPL buffer
(let ((default-directory (with-current-buffer (indium-repl-get-buffer)
default-directory)))
(indium-quit)
(indium-run-node command))
(user-error "Start a NodeJS connection with `indium-run-node' first")))
;;;###autoload
(defun indium-connect-to-nodejs (host port path)
"Open a connection to HOST:PORT/PATH."
(interactive (list
(read-from-minibuffer "Host: " "127.0.0.1")
(read-from-minibuffer "Port: " "9229")
(read-from-minibuffer "Path: ")))
(indium-nodejs--connect host port path))
;;;###autoload
(defun indium-nodejs-connect-to-url (url)
"Connect to a node process with a given URL."
(interactive (list (read-from-minibuffer "Url: ")))
(let* ((parsed-url (url-generic-parse-url url))
(host (url-host parsed-url))
(port (or (url-port parsed-url) 9229))
(path (seq-drop (car (url-path-and-query parsed-url)) 1)))
(indium-nodejs--connect host (number-to-string port) path)))
(defun indium-connect-to-nodejs ()
"Open a connection to an existing NodeJS process."
(let* ((host (indium-nodejs--host))
(port (indium-nodejs--port))
(default-directory (indium-workspace-root)))
(indium-nodejs--get-process-id host
port
(lambda (id)
(indium-nodejs--connect host port id)))))
(defun indium-nodejs--host ()
"Return the debugging host for a NodeJS process.
The host is either read from the workpace configuration file or
`indium-nodejs-default-host'."
(map-elt indium-workspace-configuration 'host indium-nodejs-default-host))
(defun indium-nodejs--port ()
"Return the debugging port for a NodeJS process.
The port is either read from the workpace configuration file or
`indium-nodejs-default-port'."
(map-elt indium-workspace-configuration 'port indium-nodejs-default-port))
(defun indium-nodejs--inspect-brk ()
"Return non nil if the option `--inspect-brk' should be used.
The setting is either read from the workpace configuration file or
`indium-nodejs-default-inspect-brk'."
(map-elt indium-workspace-configuration 'inspect-brk))
(defun indium-nodejs--get-process-id (host port callback)
"Get the id of the websocket path at HOST:PORT and evaluate CALLBACK with it."
(url-retrieve (format "http://%s:%s/json/list" host port)
(lambda (status)
(funcall callback (if (eq :error (car status))
nil
(indium-nodejs--read-process-id))))))
(defun indium-nodejs--read-process-id ()
"Return the process id read from the JSON data in the current buffer."
(when (save-match-data
(looking-at "^HTTP/.* 200 OK$"))
(goto-char (point-min))
(search-forward "\n\n")
(delete-region (point-min) (point))
(map-elt (seq-elt (json-read) 0) 'id)))
(defun indium-nodejs--connect (host port path &optional process)
"Ask the user for a websocket url HOST:PORT/PATH and connects to it.
@ -139,14 +150,15 @@ When PROCESS is non-nil, attach it to the connection."
(setf (indium-current-connection-process) process)))
t))))
(defun indium-nodejs--add-flags (command)
"Return COMMAND with the `--inspect' or `--inspect-brk' flag added."
(let ((inspect-flag (if indium-nodejs-inspect-brk
(defun indium-nodejs--command-with-flags ()
"Return the command to be run with the `--inspect' or `--inspect-brk' flag."
(let ((command (indium-nodejs--command))
(inspect-flag (if (indium-nodejs--inspect-brk)
"--inspect-brk"
"--inspect")))
(if (string-match "\\<node\\>" command)
(replace-match (concat "node " inspect-flag) nil nil command)
(error "Not a node command"))))
(user-error "Invalid command specified"))))
(defun indium-nodejs--process-filter (process output)
"Filter function for PROCESS.


+ 5
- 10
indium-v8.el View File

@ -288,7 +288,7 @@ Allowed states: `\"none\"', `\"uncaught\"', `\"all\"'."
(setq indium-v8-cache-disabled t)
(indium-v8--set-cache-disabled t))
(defun indium-v8--open-ws-connection (url websocket-url &optional on-open nodejs workspace)
(defun indium-v8--open-ws-connection (url websocket-url &optional on-open nodejs)
"Open a websocket connection to URL using WEBSOCKET-URL.
Evaluate ON-OPEN when the websocket is open, before setting up
@ -298,15 +298,12 @@ In a Chrom{e|ium} session, URL corresponds to the url of a tab,
and WEBSOCKET-URL to its associated `webSocketDebuggerUrl'.
If NODEJS is non-nil, add a `nodejs' flag to the
`indium-current-connection' to handle special cases.
If WORKSPACE is non-nil, make it the workspace directory for that
connection."
`indium-current-connection' to handle special cases."
(unless websocket-url
(user-error "Cannot open connection, another devtools instance might be open"))
(websocket-open websocket-url
:on-open (lambda (ws)
(indium-v8--handle-ws-open ws url nodejs workspace)
(indium-v8--handle-ws-open ws url nodejs)
(when on-open
(funcall on-open)))
:on-message #'indium-v8--handle-ws-message
@ -325,16 +322,14 @@ connection."
(map-put (indium-connection-props conn) 'nodejs t))
conn))
(defun indium-v8--handle-ws-open (ws url nodejs workspace)
(defun indium-v8--handle-ws-open (ws url nodejs)
"Setup indium for a new connection for the websocket WS.
URL points to the browser tab.
If NODEJS is non-nil, set an extra property in the connection.
If WORKSPACE is non-nil, make it the workspace used for the connection."
If NODEJS is non-nil, set an extra property in the connection."
(setq indium-current-connection (indium-v8--make-connection ws url nodejs))
(indium-v8--enable-tools)
(switch-to-buffer (indium-repl-get-buffer-create))
(when workspace (cd workspace))
(run-hooks 'indium-connection-open-hook))
(defun indium-v8--handle-ws-message (_ws frame)


+ 124
- 122
indium-workspace.el View File

@ -1,4 +1,4 @@
;;; indium-workspace.el --- Use local files for debugging -*- lexical-binding: t; -*-
;;; indium-workspace.el --- Indium workspace management -*- lexical-binding: t; -*-
;; Copyright (C) 2017-2018 Nicolas Petton
@ -20,82 +20,133 @@
;;; Commentary:
;; Setup a workspace for using local files when debugging JavaScript.
;; Indium workspace management.
;;
;; Files are looked up using a special `.indium' file placed in the root directory
;; of the files served.
;;
;;; Example:
;; When connecting to a backend, Indium will lookup and read a project
;; configuration file `.indium.json' in the project root directory.
;; With the following directory structure:
;;; .indium.json configuration file example:
;;
;; project/ (current directory)
;; www/
;; index.html
;; css/
;; style.css
;; js/
;; app.js
;; .indium
;; {
;; "configurations": [
;; {
;; "name": "Chrome",
;; "type": "chrome",
;; "url": "http://localhost:3333/"
;; },
;; {
;; "name": "Node server",
;; "type": "node",
;; "command": "node ./src/server.js",
;; "inspect-brk": false
;; }
;; {
;; "name": "Gulp",
;; "type": "node",
;; "command": "node ./node_modules/gulp/bin/gulp.js default",
;; "inspect-brk": true
;; }
;; ]
;; }
;;; Available settings:
;;
;; For the following URL "http://localhost:3000/js/app.js"
;; `indium-workspace-lookup-file' will return "./www/js/app.js".
;; "type": Type of runtime (currently "node" or "chrome" are supported).
;; "webRoot": Relative path to the root directory from where files are served.
;;
;; Previously used workspace directories are saved and stored in
;; "~/.emacs.d/indium-workspaces.el". You can change its location by
;; customizing `indium-workspace-file'.
;; Chrome-specific settings
;;
;; To disable persisting the list of workspaces to a file, you can set
;; `indium-workspace-use-workspace-file' to `nil'. In this case,
;; `indium-workspaces' can be manually set in your emacs.d.
;; "host": Host on which Chrome is running (defaults to "localhost").
;; "port": Port on which Chrome is running (defaults to 9222).
;; "url": Url to open when running `indium-run-chrome'.
;;
;;; Code:
;; Nodejs-specific settings
;;
;; "command": Nodejs command to start a new process. The `--inspect' flag will
;; be added automatically.
;; "inspect-brk": Whether Indium should break at the first statement (true by
;; default).
;; "host": Host on which the Node inspector is listening (defaults to "localhost").
;; "port": Port on which the Node inspector is listening (defaults to 9229).
;;; Code:
(require 'url)
(require 'seq)
(require 'map)
(require 'subr-x)
(require 'json)
(require 'indium-structs)
(require 'indium-backend)
(declare-function indium-repl-get-buffer "indium-repl.el")
(declare-function indium-connection-nodejs-p "indium-nodejs.el")
(defgroup indium-workspace nil
"Indium workspace"
:prefix "indium-worspace-"
:group 'indium)
(defcustom indium-workspace-file (locate-user-emacs-file "indium-workspaces.el")
"Location of the file used to store previously used workspace directories."
:type 'file)
(defcustom indium-workspace-use-workspace-file t
"Persist the list of worskpaces used in a file."
:type 'boolean)
(defvar indium-workspaces nil
"List of previously used workspace directories.")
(defun indium-workspace-read ()
"Ask the user to select a workspace directory.
If the current directory is within a workspace, simply return it
without prompting the user.
The selected workspace directory is added to the list of workspaces."
(when indium-workspace-use-workspace-file
(indium-workspace--read-workspaces-file))
(let ((workspace (or (indium-workspace-root)
(when (and indium-workspaces
(y-or-n-p "No workspace found. Select one? "))
(completing-read "Choose a workspace: " indium-workspaces)))))
(when (and workspace indium-workspace-use-workspace-file)
(indium-workspace--add-directory workspace)
(indium-workspace--save-workspaces-file)
workspace)))
(defvar indium-workspace-filename ".indium.json"
"Name of the configuration file containing the Indium project settings.")
(defvar indium-workspace-configuration nil
"Configuration in the settings file used for connecting.")
(defun indium-workspace-root ()
"Lookup the root workspace directory from the current buffer."
(locate-dominating-file default-directory
indium-workspace-filename))
(defun indium-workspace-ensure-setup ()
"Signal an error no workspace file can be found."
(unless (indium-workspace-root)
(error "No file .indium.json found in the current project")))
(defun indium-workspace-settings-file ()
"Lookup the filename of the settings file for the current workspace.
Return nil if not found."
(when-let ((root (indium-workspace-root)))
(expand-file-name indium-workspace-filename
root)))
(defun indium-workspace-settings ()
"Return the workspace settings read from the workspace file."
(indium-workspace-ensure-setup)
(with-temp-buffer
(insert-file-contents (indium-workspace-settings-file))
(goto-char (point-min))
(json-read)))
(defmacro with-indium-workspace-configuration (&rest body)
"Promt the users for a configuration and evaluate BODY.
During the evaluation of BODY, `indium-workspace-configuration'
is set to the choosen configuration."
(declare (indent 0) (debug t))
`(let ((indium-workspace-configuration
(or indium-workspace-configuration
(indium-workspace--read-configuration))))
,@body))
(defun indium-workspace--read-configuration ()
"Prompt for the configuration used for connecting to a backend.
If the settings file contains only one configuration, return it."
(let* ((settings (indium-workspace-settings))
(configurations (map-elt settings 'configurations))
(configuration-names (seq-map (lambda (configuration)
(map-elt configuration 'name))
configurations)))
(unless configurations
(user-error "No configuration provided in the project file"))
(if (= (seq-length configurations) 1)
(seq-elt configurations 0)
(let ((name (completing-read "Choose a configuration: "
configuration-names
nil
t)))
(seq-find (lambda (configuration)
(string-equal (map-elt configuration 'name)
name))
configurations)))))
(defun indium-workspace-lookup-file (url &optional ignore-existence)
"Return a local file matching URL for the current connection.
@ -121,19 +172,14 @@ otherwise return the path of a file that does not exist."
"Return a local file matching URL using the current Indium workspace.
When IGNORE-EXISTENCE is non-nil, also match file paths that are
not on disk."
;; Make sure we are in the correct directory so that indium can find a
;; ".indium" file.
;;
;; TODO: set the directory in the connection directly instead of relying on
;; the REPL buffer
(with-current-buffer (indium-repl-get-buffer)
(if-let ((root (indium-workspace-root)))
(let* ((path (seq-drop (car (url-path-and-query
(url-generic-parse-url url)))
1))
(file (expand-file-name path root)))
(when (or ignore-existence (file-regular-p file))
file)))))
(indium-workspace-ensure-setup)
(let* ((root (indium-workspace-root))
(path (seq-drop (car (url-path-and-query
(url-generic-parse-url url)))
1))
(file (expand-file-name path root)))
(when (or ignore-existence (file-regular-p file))
file)))
(defun indium-workspace-make-url (file)
"Return the url associated with the local FILE."
@ -155,11 +201,12 @@ If the current connection doesn't use the file protocol, return nil."
(defun indium-workspace--make-url-using-workspace (file)
"Return the url associated with the local FILE.
The url is built using `indium-workspace-root'."
(if-let ((root (indium-workspace-root)))
(let* ((url (indium-workspace--url-basepath (indium-current-connection-url)))
(path (file-relative-name file root)))
(setf (url-filename url) (indium-workspace--absolute-path path))
(url-recreate-url url))))
(indium-workspace-ensure-setup)
(let* ((root (indium-workspace-root))
(url (indium-workspace--url-basepath (indium-current-connection-url)))
(path (file-relative-name file root)))
(setf (url-filename url) (indium-workspace--absolute-path path))
(url-recreate-url url)))
(defun indium-workspace--file-protocol-p ()
"Return non-nil if the current connection use the file protocol."
@ -183,50 +230,5 @@ The path and query string of URL are stripped."
(url-port urlobj)
nil nil nil t)))
(defun indium-workspace-root ()
"Lookup the root workspace directory from the current buffer."
(indium-workspace-locate-dominating-file default-directory ".indium"))
(defun indium-workspace-locate-dominating-file (file name)
"Look up the directory hierarchy from FILE for a directory containing NAME.
Stop at the first parent directory containing a file NAME,
and return the directory. Return nil if not found.
Instead of a string, NAME can also be a predicate taking one argument
\(a directory) and returning a non-nil value if that directory is the one for
which we're looking."
;; copied from projectile.el, itself copied from files.el (stripped comments)
;; emacs-24 bzr branch 2014-03-28 10:20
(setq file (abbreviate-file-name file))
(let ((root nil)
try)
(while (not (or root
(null file)
(string-match locate-dominating-stop-dir-regexp file)))
(setq try (if (stringp name)
(file-exists-p (expand-file-name name file))
(funcall name file)))
(cond (try (setq root file))
((equal file (setq file (file-name-directory
(directory-file-name file))))
(setq file nil))))
(and root (expand-file-name (file-name-as-directory root)))))
(defun indium-workspace--add-directory (directory)
"Add DIRECTORY to the list of workspaces."
(add-to-list 'indium-workspaces directory))
(defun indium-workspace--save-workspaces-file ()
"Save previously used workspace directories."
(make-directory (file-name-directory indium-workspace-file) t)
(with-temp-file indium-workspace-file
(emacs-lisp-mode)
(insert ";; This file is automatically generated by Indium.")
(newline)
(insert (format "(setq %s '%S)" "indium-workspaces" indium-workspaces))))
(defun indium-workspace--read-workspaces-file ()
"Read the workspaces file and set `indium-worspaces'."
(load indium-workspace-file t))
(provide 'indium-workspace)
;;; indium-workspace.el ends here

+ 9
- 0
test/fixtures/.indium.json View File

@ -0,0 +1,9 @@
{
"configurations": [
{
"name": "node",
"type": "node",
"command": "node ./test.js"
}
]
}

+ 29
- 21
test/integration/indium-nodejs-integration-test.el View File

@ -43,31 +43,39 @@
(indium-quit)))
(it "should be able to start a node process and connect to it"
(expect indium-current-connection :to-be nil)
(indium-run-node "node ../fixtures/test.js")
(sleep-for 2)
(expect indium-current-connection :not :to-be nil))
(with-indium-test-fs
(spy-on 'indium-workspace-root :and-return-value default-directory)
(expect indium-current-connection :to-be nil)
(indium-launch)
(sleep-for 2)
(expect indium-current-connection :not :to-be nil)))
(it "should not try to open a new connection on process output"
(spy-on 'indium-nodejs--connect-to-process :and-call-through)
(expect indium-current-connection :to-be nil)
(indium-run-node "node ../fixtures/test-with-output.js")
(sleep-for 2)
(expect #'indium-nodejs--connect-to-process :to-have-been-called-times 1))
(with-indium-test-fs
(spy-on 'indium-workspace-root :and-return-value default-directory)
(spy-on 'indium-nodejs--connect-to-process :and-call-through)
(expect indium-current-connection :to-be nil)
(indium-launch)
(sleep-for 2)
(expect #'indium-nodejs--connect-to-process :to-have-been-called-times 1)))
(it "should create a REPL buffer upon connection"
(expect (get-buffer (indium-repl-buffer-name)) :to-be nil)
(indium-run-node "node ../fixtures/test.js")
(sleep-for 2)
(expect (get-buffer (indium-repl-buffer-name)) :not :to-be nil))
(with-indium-test-fs
(spy-on 'indium-workspace-root :and-return-value default-directory)
(expect (get-buffer (indium-repl-buffer-name)) :to-be nil)
(indium-launch)
(sleep-for 2)
(expect (get-buffer (indium-repl-buffer-name)) :not :to-be nil)))
(it "should run hooks when opening a connection"
(spy-on 'foo)
(add-hook 'indium-connection-open-hook #'foo)
(indium-run-node "node ../fixtures/test.js")
(sleep-for 2)
(expect #'foo :to-have-been-called)
(remove-hook 'indium-connection-open-hook #'foo)))
(provide 'indium-nodejs-integration-test)
(with-indium-test-fs
(spy-on 'indium-workspace-root :and-return-value default-directory)
(spy-on 'ignore)
(add-hook 'indium-connection-open-hook #'ignore)
(indium-launch)
(sleep-for 2)
(expect #'ignore :to-have-been-called)
(remove-hook 'indium-connection-open-hook #'ignore)))
(provide 'indium-nodejs-integration-test))
;;; indium-nodejs-integration-test.el ends here

+ 20
- 7
test/test-helper.el View File

@ -26,6 +26,7 @@
(require 'seq)
(require 'map)
(require 'assess)
(when (require 'undercover nil t)
(setq undercover-force-coverage t)
@ -33,6 +34,9 @@
(advice-add 'undercover-report :after #'print-coverage-report-safe)
(defvar indium-nodejs--test-fs
'((".indium.json" "{\"configurations\": [{\"type\": \"node\", \"command\": \"node\"}]}")))
(defun print-coverage-report-safe (&rest _)
(ignore-errors
(print-coverage-report)))
@ -86,17 +90,26 @@ a temporary file, which is removed afterwards."
`(with-indium-connection (make-indium-connection :backend 'fake)
,@body))
(defmacro with-nodejs-connection (&rest body)
"Run BODY within a NodeJS connection on a process on fixtures/test.js."
(defmacro with-indium-test-fs (&rest body)
"Run BODY with filesystem set to `indium-nodejs--test-fs'."
(declare (indent 0) (debug t))
`(assess-with-filesystem indium-nodejs--test-fs
,@body))
(defmacro with-indium-nodejs-connection (&rest body)
"Run BODY within a NodeJS connection."
(declare (indent 0))
`(progn
(ignore-errors (exec-path-from-shell-initialize))
(indium-run-node "node")
(wait-for-repl-buffer)
,@body
(indium-quit)))
(with-indium-test-fs
(indium-launch)
(wait-for-repl-buffer)
,@body
(indium-quit))))
(defun wait-for-repl-buffer (&optional retry)
"Wait 200ms for the REPL buffer to be ready.
If RETRY is the number of retries, is non-nil."
(unless retry (setq retry 10))
(sleep-for 0.2)
(unless (or (get-buffer (indium-repl-buffer-name))
@ -107,7 +120,7 @@ a temporary file, which is removed afterwards."
(defmacro with-repl-buffer (&rest body)
"Execute BODY within a REPL buffer with a NodeJS connection."
(declare (indent 0))
`(with-nodejs-connection
`(with-indium-nodejs-connection
(with-current-buffer (indium-repl-buffer-name)
,@body)))


+ 55
- 47
test/unit/indium-breakpoint-test.el View File

@ -112,31 +112,34 @@
(expect (indium-breakpoint--overlay-on-current-line) :to-be ov))))
(it "can put a breakpoint on the current line"
(with-js2-buffer "let a = 1;"
(goto-char (point-min))
(expect (indium-breakpoint-on-current-line-p) :to-be nil)
(indium-breakpoint-add)
(expect (indium-breakpoint-on-current-line-p) :to-be-truthy)))
(with-indium-test-fs
(with-js2-buffer "let a = 1;"
(goto-char (point-min))
(expect (indium-breakpoint-on-current-line-p) :to-be nil)
(indium-breakpoint-add)
(expect (indium-breakpoint-on-current-line-p) :to-be-truthy))))
(it "can edit a breakpoint on the current line"
(spy-on #'read-from-minibuffer :and-return-value "new condition")
(spy-on #'indium-breakpoint-remove :and-call-through)
(spy-on #'indium-breakpoint-add :and-call-through)
(with-js2-buffer "let a = 1;"
(goto-char (point-min))
(indium-breakpoint-add "old condition")
(indium-breakpoint-edit-condition)
(expect #'read-from-minibuffer :to-have-been-called)
(expect #'indium-breakpoint-remove :to-have-been-called)
(expect #'indium-breakpoint-add :to-have-been-called-with "new condition"))))
(with-indium-test-fs
(with-js2-buffer "let a = 1;"
(goto-char (point-min))
(indium-breakpoint-add "old condition")
(indium-breakpoint-edit-condition)
(expect #'read-from-minibuffer :to-have-been-called)
(expect #'indium-breakpoint-remove :to-have-been-called)
(expect #'indium-breakpoint-add :to-have-been-called-with "new condition")))))
(describe "Breakpoint duplication handling"
(it "can add a breakpoint multiple times on the same line without duplicating it"
(with-temp-buffer
(indium-breakpoint-add)
(let ((number-of-overlays (seq-length (overlays-in (point-min) (point-max)))))
(indium-breakpoint-add)
(expect (seq-length (overlays-in (point-min) (point-max))) :to-equal number-of-overlays)))))
(with-indium-test-fs
(with-temp-buffer
(indium-breakpoint-add)
(let ((number-of-overlays (seq-length (overlays-in (point-min) (point-max)))))
(indium-breakpoint-add)
(expect (seq-length (overlays-in (point-min) (point-max))) :to-equal number-of-overlays))))))
(describe "Handling overlays in breakpoints"
(it "adding overlays should store the overlay in the breakpoint"
@ -162,45 +165,50 @@
(describe "Keeping track of local breakpoints in buffers"
(it "should track breakpoints when added"
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table :weakness t)))
(indium-breakpoint-add)
(expect (seq-length (map-keys indium-breakpoint--local-breakpoints)) :to-be 1)
(expect (car (map-values indium-breakpoint--local-breakpoints)) :to-be (current-buffer)))))
(with-indium-test-fs
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table :weakness t)))
(indium-breakpoint-add)
(expect (seq-length (map-keys indium-breakpoint--local-breakpoints)) :to-be 1)
(expect (car (map-values indium-breakpoint--local-breakpoints)) :to-be (current-buffer))))))
(it "should untrack breakpoints when removed"
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table :weakness t)))
(indium-breakpoint-add)
(indium-breakpoint-remove)
(expect (seq-length (map-keys indium-breakpoint--local-breakpoints)) :to-be 0))))
(with-indium-test-fs
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table :weakness t)))
(indium-breakpoint-add)
(indium-breakpoint-remove)
(expect (seq-length (map-keys indium-breakpoint--local-breakpoints)) :to-be 0)))))
(it "should untrack breakpoints when killing a buffer"
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table :weakness t)))
(indium-breakpoint-add)
(kill-buffer)
(expect (seq-length (map-keys indium-breakpoint--local-breakpoints)) :to-be 0))))
(with-indium-test-fs
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table :weakness t)))
(indium-breakpoint-add)
(kill-buffer)
(expect (seq-length (map-keys indium-breakpoint--local-breakpoints)) :to-be 0)))))
(it "can get breakpoints by id"
(let ((indium-breakpoint--local-breakpoints (make-hash-table))
(brk (make-indium-breakpoint :id 'foo)))
(map-put indium-breakpoint--local-breakpoints
brk
'bar)
(expect (indium-breakpoint-breakpoint-with-id 'foo) :to-be brk))))
(with-indium-test-fs
(let ((indium-breakpoint--local-breakpoints (make-hash-table))
(brk (make-indium-breakpoint :id 'foo)))
(map-put indium-breakpoint--local-breakpoints
brk
'bar)
(expect (indium-breakpoint-breakpoint-with-id 'foo) :to-be brk)))))
(describe "Breakpoint resolution"
(it "should be able to unresolve breakpoints"
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table)))
(indium-breakpoint-add)
(let ((brk (indium-breakpoint-at-point)))
;; Fake the resolution of the breakpoint
(setf (indium-breakpoint-id (indium-breakpoint-at-point)) 'foo)
(expect (indium-breakpoint-unresolved-p brk) :to-be nil)
(indium-breakpoint--unresolve-all-breakpoints)
(expect (indium-breakpoint-unresolved-p brk) :to-be-truthy))))))
(with-indium-test-fs
(with-js2-buffer "let a = 2;"
(let ((indium-breakpoint--local-breakpoints (make-hash-table)))
(indium-breakpoint-add)
(let ((brk (indium-breakpoint-at-point)))
;; Fake the resolution of the breakpoint
(setf (indium-breakpoint-id (indium-breakpoint-at-point)) 'foo)
(expect (indium-breakpoint-unresolved-p brk) :to-be nil)
(indium-breakpoint--unresolve-all-breakpoints)
(expect (indium-breakpoint-unresolved-p brk) :to-be-truthy)))))))
(provide 'indium-breakpoint-test)
;;; indium-breakpoint-test.el ends here

+ 1
- 1
test/unit/indium-nodejs-test.el View File

@ -41,7 +41,7 @@
;; Regression for GitHub issue #150
(expect (indium-nodejs--add-flags "MY_ENV=\"val\" node foo")
:to-equal "MY_ENV=\"val\" node --inspect foo"))
(let ((indium-nodejs-inspect-brkt))
(let ((indium-nodejs-inspect-brk))
(expect (indium-nodejs--add-flags "node foo")
:to-equal "node --inspect-brk foo")))


Loading…
Cancel
Save